diff --git a/RetailCoder.VBE/Common/LogLevelHelper.cs b/RetailCoder.VBE/Common/LogLevelHelper.cs index d60497aaa7..6636318814 100644 --- a/RetailCoder.VBE/Common/LogLevelHelper.cs +++ b/RetailCoder.VBE/Common/LogLevelHelper.cs @@ -1,4 +1,5 @@ -using NLog; +using System.Linq; +using NLog; using NLog.Config; using System; using System.Collections.Generic; @@ -28,6 +29,16 @@ private static IEnumerable GetLogLevels() return logLevels; } + public static int MinLogLevel() + { + return GetLogLevels().Min(lvl => lvl.Ordinal); + } + + public static int MaxLogLevel() + { + return GetLogLevels().Max(lvl => lvl.Ordinal); + } + public static void SetMinimumLogLevel(LogLevel minimumLogLevel) { var loggingRules = LogManager.Configuration.LoggingRules; diff --git a/RetailCoder.VBE/Inspections/Abstract/InspectionResultBase.cs b/RetailCoder.VBE/Inspections/Abstract/InspectionResultBase.cs index e3f9e16394..24772a4fe7 100644 --- a/RetailCoder.VBE/Inspections/Abstract/InspectionResultBase.cs +++ b/RetailCoder.VBE/Inspections/Abstract/InspectionResultBase.cs @@ -68,7 +68,7 @@ protected InspectionResultBase(IInspection inspection, QualifiedModuleName quali /// /// Gets the information needed to select the target instruction in the VBE. /// - public QualifiedSelection QualifiedSelection + public virtual QualifiedSelection QualifiedSelection { get { @@ -85,13 +85,13 @@ public QualifiedSelection QualifiedSelection /// /// Gets all available "quick fixes" for a code inspection result. /// - public virtual IEnumerable QuickFixes { get { return new QuickFixBase[] {}; } } + public virtual IEnumerable QuickFixes { get { return Enumerable.Empty(); } } public bool HasQuickFixes { get { return QuickFixes.Any(); } } public virtual QuickFixBase DefaultQuickFix { get { return QuickFixes.FirstOrDefault(); } } - public int CompareTo(IInspectionResult other) + public virtual int CompareTo(IInspectionResult other) { return Inspection.CompareTo(other.Inspection); } diff --git a/RetailCoder.VBE/Inspections/Concrete/Inspector.cs b/RetailCoder.VBE/Inspections/Concrete/Inspector.cs index 90f3dae403..2945a5191b 100644 --- a/RetailCoder.VBE/Inspections/Concrete/Inspector.cs +++ b/RetailCoder.VBE/Inspections/Concrete/Inspector.cs @@ -13,6 +13,7 @@ using Rubberduck.Parsing.VBA; using Rubberduck.Settings; using Rubberduck.UI; +using Rubberduck.Inspections.Results; namespace Rubberduck.Inspections.Concrete { @@ -22,6 +23,7 @@ public class Inspector : IInspector { private readonly IGeneralConfigService _configService; private readonly List _inspections; + private readonly int AGGREGATION_THRESHOLD = 128; public Inspector(IGeneralConfigService configService, IEnumerable inspections) { @@ -94,7 +96,14 @@ public async Task> FindIssuesAsync(RubberduckPars LogManager.GetCurrentClassLogger().Error(e); } state.OnStatusMessageUpdate(RubberduckUI.ResourceManager.GetString("ParserState_" + state.Status, UI.Settings.Settings.Culture)); // should be "Ready" - return allIssues; + + var issuesByType = allIssues.GroupBy(issue => issue.GetType()) + .ToDictionary(grouping => grouping.Key, grouping => grouping.ToList()); + return issuesByType.Where(kv => kv.Value.Count <= AGGREGATION_THRESHOLD) + .SelectMany(kv => kv.Value) + .Union(issuesByType.Where(kv => kv.Value.Count > AGGREGATION_THRESHOLD) + .Select(kv => new AggregateInspectionResult(kv.Value.OrderBy(i => i.QualifiedSelection).ToList()))); + //return allIssues; } private IReadOnlyList GetParseTreeResults(Configuration config, RubberduckParserState state) diff --git a/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs b/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs index 2e4b60325b..eeb40f057c 100644 --- a/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs +++ b/RetailCoder.VBE/Inspections/QuickFixes/DeclareAsExplicitVariantQuickFix.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using Antlr4.Runtime; using Rubberduck.Inspections.Abstract; @@ -25,7 +26,7 @@ public override void Fix() // DeclareExplicitVariant() overloads return empty string if context is null Selection selection; - var fix = DeclareExplicitVariant(Context as VBAParser.VariableSubStmtContext, out originalInstruction, out selection); + var fix = DeclareExplicitVariant(Context as VBAParser.VariableSubStmtContext, contextLines, out originalInstruction, out selection); if (!string.IsNullOrEmpty(fix)) { // maintain original indentation for a variable declaration @@ -34,7 +35,7 @@ public override void Fix() if (string.IsNullOrEmpty(originalInstruction)) { - fix = DeclareExplicitVariant(Context as VBAParser.ConstSubStmtContext, out originalInstruction, out selection); + fix = DeclareExplicitVariant(Context as VBAParser.ConstSubStmtContext, contextLines, out originalInstruction, out selection); } if (string.IsNullOrEmpty(originalInstruction)) @@ -100,7 +101,7 @@ private string DeclareExplicitVariant(VBAParser.ArgContext context, out string i return fix; } - private string DeclareExplicitVariant(VBAParser.VariableSubStmtContext context, out string instruction, out Selection selection) + private string DeclareExplicitVariant(VBAParser.VariableSubStmtContext context, string contextLines, out string instruction, out Selection selection) { if (context == null) { @@ -110,17 +111,19 @@ private string DeclareExplicitVariant(VBAParser.VariableSubStmtContext context, } var parent = (ParserRuleContext)context.Parent.Parent; - instruction = parent.GetText(); selection = parent.GetSelection(); + instruction = contextLines.Substring(selection.StartColumn - 1); var variable = context.GetText(); var replacement = context.identifier().GetText() + ' ' + Tokens.As + ' ' + Tokens.Variant; - var result = instruction.Replace(variable, replacement); + var insertIndex = instruction.IndexOf(variable, StringComparison.Ordinal); + var result = instruction.Substring(0, insertIndex) + + replacement + instruction.Substring(insertIndex + variable.Length); return result; } - private string DeclareExplicitVariant(VBAParser.ConstSubStmtContext context, out string instruction, out Selection selection) + private string DeclareExplicitVariant(VBAParser.ConstSubStmtContext context, string contextLines, out string instruction, out Selection selection) { if (context == null) { @@ -130,8 +133,8 @@ private string DeclareExplicitVariant(VBAParser.ConstSubStmtContext context, out } var parent = (ParserRuleContext)context.Parent; - instruction = parent.GetText(); - selection = parent.GetSelection(); + selection = parent.GetSelection(); + instruction = contextLines.Substring(selection.StartColumn - 1); var constant = context.GetText(); var replacement = context.identifier().GetText() + ' ' @@ -139,7 +142,9 @@ private string DeclareExplicitVariant(VBAParser.ConstSubStmtContext context, out + context.EQ().GetText() + ' ' + context.expression().GetText(); - var result = instruction.Replace(constant, replacement); + var insertIndex = instruction.IndexOf(constant, StringComparison.Ordinal); + var result = instruction.Substring(0, insertIndex) + + replacement + instruction.Substring(insertIndex + constant.Length); return result; } } diff --git a/RetailCoder.VBE/Inspections/Resources/InspectionsUI.Designer.cs b/RetailCoder.VBE/Inspections/Resources/InspectionsUI.Designer.cs index c280ba5b72..5ba2fee303 100644 --- a/RetailCoder.VBE/Inspections/Resources/InspectionsUI.Designer.cs +++ b/RetailCoder.VBE/Inspections/Resources/InspectionsUI.Designer.cs @@ -60,6 +60,15 @@ internal InspectionsUI() { } } + /// + /// Looks up a localized string similar to {0} ({1} results). + /// + public static string AggregateInspectionResultFormat { + get { + return ResourceManager.GetString("AggregateInspectionResultFormat", resourceCulture); + } + } + /// /// Looks up a localized string similar to Parameter is passed by value, but is assigned a new value/reference. Consider making a local copy instead if the caller isn't supposed to know the new value. If the caller should see the new value, the parameter should be passed ByRef instead, and you have a bug.. /// diff --git a/RetailCoder.VBE/Inspections/Resources/InspectionsUI.de.resx b/RetailCoder.VBE/Inspections/Resources/InspectionsUI.de.resx index 45e714c169..2e169d28a1 100644 --- a/RetailCoder.VBE/Inspections/Resources/InspectionsUI.de.resx +++ b/RetailCoder.VBE/Inspections/Resources/InspectionsUI.de.resx @@ -568,7 +568,7 @@ Falls der Parameter 'null' sein kann, bitte dieses Auftreten ignorieren. 'null' Code, der undeklarierte Variablen verwendet, kompiliert nicht wenn 'Option Explicit' spezifiziert wird. Undeklarierte Variablen sind immer vom Typ 'Variant', was unnötige Zusatzkosten in Ausführungszeit und Speicherverbauch verursacht. - Add property get + Addieren 'Property Get' Das Schlüsselwort 'Public' kann nur auf Modulebene verwendet werden; Sein Konterpart 'Private' kann auch nur auf Modulebene verwendet werden. 'Dim' jedoch kann verwendet werden, um sowohl modulweite als auch prozedurweite Variablen zu deklarieren. Um der Konsistenz Willen ist es besser, 'Dim' nur für lokale Variablen zu verwenden, also 'Private' statt 'Dim' auf Modulebene zu verwenden. @@ -588,7 +588,10 @@ Falls der Parameter 'null' sein kann, bitte dieses Auftreten ignorieren. 'null' Die Modulvariable '{0}' ist mit dem 'Dim'-Schlüsselwort deklariert. - + Ein Member ist als Funktion geschrieben, aber wird wie eine Prozedur verwendet. Falls die Funktion nicht rekursiv ist, sollten Sie in Erwägung ziehen, die 'Function' in ein 'Sub' zu konvertieren. Falls die Funktion rekursiv ist, verwendet keiner der externen Aufrufer den Rückgabewert. - + + {0} ({1} Ergebnisse) + + \ No newline at end of file diff --git a/RetailCoder.VBE/Inspections/Resources/InspectionsUI.fr.resx b/RetailCoder.VBE/Inspections/Resources/InspectionsUI.fr.resx index 8ff6827d1a..14de829bee 100644 --- a/RetailCoder.VBE/Inspections/Resources/InspectionsUI.fr.resx +++ b/RetailCoder.VBE/Inspections/Resources/InspectionsUI.fr.resx @@ -601,4 +601,7 @@ Si le paramètre peut être nul, ignorer ce résultat; passer une valeur nulle Assignation de '{0}' réfère implicitement au membre par défaut de la classe '{1}' + + {0} ({1} résultats) + \ No newline at end of file diff --git a/RetailCoder.VBE/Inspections/Resources/InspectionsUI.resx b/RetailCoder.VBE/Inspections/Resources/InspectionsUI.resx index 03a1a6b185..7de01c9287 100644 --- a/RetailCoder.VBE/Inspections/Resources/InspectionsUI.resx +++ b/RetailCoder.VBE/Inspections/Resources/InspectionsUI.resx @@ -602,4 +602,8 @@ If the parameter can be null, ignore this inspection result; passing a null valu Assignment to '{0}' implicitly assigns default member of class '{1}' + + {0} ({1} results) + {0} inpection description, {1} result count + \ No newline at end of file diff --git a/RetailCoder.VBE/Inspections/Results/AggregateInspectionResult.cs b/RetailCoder.VBE/Inspections/Results/AggregateInspectionResult.cs new file mode 100644 index 0000000000..308ec87317 --- /dev/null +++ b/RetailCoder.VBE/Inspections/Results/AggregateInspectionResult.cs @@ -0,0 +1,71 @@ +using Rubberduck.Inspections.Abstract; +using Rubberduck.Inspections.Resources; +using Rubberduck.VBEditor; +using System.Collections.Generic; +using System.Linq; +using Antlr4.Runtime; + +namespace Rubberduck.Inspections.Results +{ + class AggregateInspectionResult: InspectionResultBase + { + private readonly List _results; + private readonly IInspectionResult _result; + + public AggregateInspectionResult(List results) + : base(results[0].Inspection, results[0].QualifiedSelection.QualifiedName, ParserRuleContext.EmptyContext) + { + _results = results; + _result = results[0]; + } + + public IReadOnlyList IndividualResults { get { return _results; } } + + public override string Description + { + get + { + return string.Format(InspectionsUI.AggregateInspectionResultFormat, _result.Inspection.Description, _results.Count); + } + } + + public override QualifiedSelection QualifiedSelection + { + get + { + return _result.QualifiedSelection; + } + } + + public override IEnumerable QuickFixes + { + get { return _result.QuickFixes == null ? base.QuickFixes : new[] { _result.QuickFixes.FirstOrDefault() }; } + } + + public override QuickFixBase DefaultQuickFix { get { return _result.QuickFixes == null ? null : _result.QuickFixes.FirstOrDefault(); } } + + public override int CompareTo(IInspectionResult other) + { + if (other == this) + { + return 0; + } + var aggregated = other as AggregateInspectionResult; + if (aggregated == null) + { + return -1; + } + if (_results.Count != aggregated._results.Count) { + return _results.Count - aggregated._results.Count; + } + for (var i = 0; i < _results.Count; i++) + { + if (_results[i].CompareTo(aggregated._results[i]) != 0) + { + return _results[i].CompareTo(aggregated._results[i]); + } + } + return 0; + } + } +} diff --git a/RetailCoder.VBE/Rubberduck.csproj b/RetailCoder.VBE/Rubberduck.csproj index 361dae4af7..406b3c85ac 100644 --- a/RetailCoder.VBE/Rubberduck.csproj +++ b/RetailCoder.VBE/Rubberduck.csproj @@ -366,6 +366,12 @@ + + True + True + InspectionsUI.resx + + @@ -519,11 +525,6 @@ - - True - True - InspectionsUI.resx - @@ -1038,8 +1039,8 @@ PublicResXFileCodeGenerator - InspectionsUI.Designer.cs Designer + InspectionsUI.Designer.cs ResXFileCodeGenerator diff --git a/RetailCoder.VBE/Settings/GeneralSettings.cs b/RetailCoder.VBE/Settings/GeneralSettings.cs index 463db58024..74032f9c55 100644 --- a/RetailCoder.VBE/Settings/GeneralSettings.cs +++ b/RetailCoder.VBE/Settings/GeneralSettings.cs @@ -1,5 +1,7 @@ -using NLog; +using System; +using NLog; using System.Xml.Serialization; +using Rubberduck.Common; namespace Rubberduck.Settings { @@ -23,7 +25,27 @@ public class GeneralSettings : IGeneralSettings public bool AutoSaveEnabled { get; set; } public int AutoSavePeriod { get; set; } public char Delimiter { get; set; } - public int MinimumLogLevel { get; set; } + + private int _logLevel; + public int MinimumLogLevel + { + get { return _logLevel; } + set + { + if (value < LogLevelHelper.MinLogLevel()) + { + _logLevel = LogLevelHelper.MinLogLevel(); + } + else if (value > LogLevelHelper.MaxLogLevel()) + { + _logLevel = LogLevelHelper.MaxLogLevel(); + } + else + { + _logLevel = value; + } + } + } public GeneralSettings() { diff --git a/RetailCoder.VBE/UI/Inspections/InspectionResultsViewModel.cs b/RetailCoder.VBE/UI/Inspections/InspectionResultsViewModel.cs index 085c2cf8f1..01209364bb 100644 --- a/RetailCoder.VBE/UI/Inspections/InspectionResultsViewModel.cs +++ b/RetailCoder.VBE/UI/Inspections/InspectionResultsViewModel.cs @@ -12,6 +12,7 @@ using Rubberduck.Common; using Rubberduck.Inspections.Abstract; using Rubberduck.Inspections.Resources; +using Rubberduck.Inspections.Results; using Rubberduck.Parsing.VBA; using Rubberduck.Settings; using Rubberduck.UI.Command; @@ -353,12 +354,16 @@ private void ExecuteQuickFixInModuleCommand(object parameter) return; } - var items = _results.Where(result => result.Inspection == SelectedInspection - && result.QualifiedSelection.QualifiedName == selectedResult.QualifiedSelection.QualifiedName) - .Select(item => item.QuickFixes.Single(fix => fix.GetType() == _defaultFix.GetType())) - .OrderByDescending(item => item.Selection.Selection.EndLine) - .ThenByDescending(item => item.Selection.Selection.EndColumn); + var filteredResults = _results + .Where(result => result.Inspection == SelectedInspection + && result.QualifiedSelection.QualifiedName == selectedResult.QualifiedSelection.QualifiedName) + .ToList(); + var items = filteredResults.Where(result => !(result is AggregateInspectionResult)) + .Select(item => item.QuickFixes.Single(fix => fix.GetType() == _defaultFix.GetType())) + .Union(filteredResults.OfType() + .SelectMany(aggregate => aggregate.IndividualResults.Select(result => result.QuickFixes.Single(fix => fix.GetType() == _defaultFix.GetType())))) + .OrderByDescending(fix => fix.Selection); ExecuteQuickFixes(items); } diff --git a/Rubberduck.Parsing/VBA/RubberduckParserState.cs b/Rubberduck.Parsing/VBA/RubberduckParserState.cs index 3fa5c7daf6..c70012eab0 100644 --- a/Rubberduck.Parsing/VBA/RubberduckParserState.cs +++ b/Rubberduck.Parsing/VBA/RubberduckParserState.cs @@ -239,9 +239,12 @@ public void RefreshProjects(IVBE vbe) private void RemoveProject(string projectId, bool notifyStateChanged = false) { - if (_projects.ContainsKey(projectId)) + lock (_projects) { - _projects.Remove(projectId); + if (_projects.ContainsKey(projectId)) + { + _projects.Remove(projectId); + } } ClearStateCache(projectId, notifyStateChanged); @@ -251,7 +254,10 @@ public List Projects { get { - return new List(_projects.Values); + lock(_projects) + { + return new List(_projects.Values); + } } } @@ -303,11 +309,20 @@ public void SetModuleState(IVBComponent component, ParserState state, SyntaxErro var projectId = component.Collection.Parent.HelpFile; IVBProject project = null; - foreach (var item in _projects) + lock (_projects) { - if (item.Value.HelpFile == projectId) + foreach (var item in _projects) { - project = project != null ? null : item.Value; + if (item.Value.HelpFile == projectId) + { + if (project != null) + { + // ghost component detected, abort project iteration + project = null; + break; + } + project = item.Value; + } } } @@ -1077,6 +1092,7 @@ public void Dispose() _moduleStates.Clear(); _declarationSelections.Clear(); + // no lock because nobody should try to update anything here _projects.Clear(); _isDisposed = true; diff --git a/Rubberduck.VBEEditor/Application/ExcelApp.cs b/Rubberduck.VBEEditor/Application/ExcelApp.cs index 376d10ccde..b6e8ae7411 100644 --- a/Rubberduck.VBEEditor/Application/ExcelApp.cs +++ b/Rubberduck.VBEEditor/Application/ExcelApp.cs @@ -1,4 +1,7 @@ -using Rubberduck.VBEditor.SafeComWrappers.Abstract; +using Path = System.IO.Path; +using System.Runtime.InteropServices; +using Microsoft.Office.Interop.Visio; +using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.VBEditor.Application { @@ -9,14 +12,19 @@ public ExcelApp(IVBE vbe) : base(vbe, "Excel") { } public override void Run(QualifiedMemberName qualifiedMemberName) { - string call = GenerateMethodCall(qualifiedMemberName); + var call = GenerateMethodCall(qualifiedMemberName); Application.Run(call); } - protected virtual string GenerateMethodCall(QualifiedMemberName qualifiedMemberName) + protected virtual string GenerateMethodCall(QualifiedMemberName qualifiedMemberName) { - var documentName = qualifiedMemberName.QualifiedModuleName.ProjectDisplayName; - return string.Concat(documentName, "!", qualifiedMemberName.ToString()); + var module = qualifiedMemberName.QualifiedModuleName; + + var documentName = string.IsNullOrEmpty(module.Project.FileName) + ? module.ProjectDisplayName + : Path.GetFileName(module.Project.FileName); + + return string.Format("'{0}'!{1}", documentName.Replace("'", "''"), qualifiedMemberName); } } } diff --git a/Rubberduck.VBEEditor/QualifiedModuleName.cs b/Rubberduck.VBEEditor/QualifiedModuleName.cs index df9ba85fa7..7b0fa5b3da 100644 --- a/Rubberduck.VBEEditor/QualifiedModuleName.cs +++ b/Rubberduck.VBEEditor/QualifiedModuleName.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Text.RegularExpressions; using Rubberduck.VBEditor.SafeComWrappers.Abstract; namespace Rubberduck.VBEditor @@ -122,6 +123,9 @@ public QualifiedMemberName QualifyMemberName(string member) private readonly string _projectPath; public string ProjectPath { get { return _projectPath; } } + private static readonly Regex CaptionProjectRegex = new Regex(@"^(?:[^-]+)(?:\s-\s)(?.+)(?:\s-\s.*)?$"); + private static readonly Regex OpenModuleRegex = new Regex(@"^(?.+)(?\s-\s\[.*\((Code|UserForm)\)\])$"); + // because this causes a flicker in the VBE, we only want to do it once. // we also want to defer it as long as possible because it is only // needed in a couple places, and QualifiedModuleName is used in many places. @@ -146,17 +150,18 @@ public string ProjectDisplayName vbe.ActiveVBProject = _project; } - var windowCaptionElements = mainWindow.Caption.Split(' ').ToList(); - // without an active code pane: {"Microsoft", "Visual", "Basic", "for", "Applications", "-", "Book2"} - // with an active code pane: {"Microsoft", "Visual", "Basic", "for", "Applications", "-", "Book2", "-", "[Thisworkbook", "(Code)]"} - // so we need to index of the first "-" element; the display name is the element next to that. - _projectDisplayName = windowCaptionElements[windowCaptionElements.IndexOf("-") + 1]; - return _projectDisplayName; - } - catch - { - return string.Empty; + var caption = mainWindow.Caption; + if (CaptionProjectRegex.IsMatch(caption)) + { + caption = CaptionProjectRegex.Matches(caption)[0].Groups["project"].Value; + _projectDisplayName = OpenModuleRegex.IsMatch(caption) + ? OpenModuleRegex.Matches(caption)[0].Groups["project"].Value + : caption; + } } + // ReSharper disable once EmptyGeneralCatchClause + catch { } + return _projectDisplayName; } } } diff --git a/Rubberduck.VBEEditor/QualifiedSelection.cs b/Rubberduck.VBEEditor/QualifiedSelection.cs index a25fb7218b..fab6782687 100644 --- a/Rubberduck.VBEEditor/QualifiedSelection.cs +++ b/Rubberduck.VBEEditor/QualifiedSelection.cs @@ -1,6 +1,8 @@ -namespace Rubberduck.VBEditor +using System; + +namespace Rubberduck.VBEditor { - public struct QualifiedSelection + public struct QualifiedSelection : IComparable { public QualifiedSelection(QualifiedModuleName qualifiedName, Selection selection) { @@ -14,6 +16,16 @@ public QualifiedSelection(QualifiedModuleName qualifiedName, Selection selection private readonly Selection _selection; public Selection Selection { get { return _selection; } } + public int CompareTo(QualifiedSelection other) + { + if (other.QualifiedName != QualifiedName) + { + return string.Compare(QualifiedName.ToString(), other.QualifiedName.ToString(), StringComparison.Ordinal); + } + + return Selection.CompareTo(other.Selection); + } + public override string ToString() { return string.Concat(QualifiedName, " ", Selection); diff --git a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBComponent.cs b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBComponent.cs index 6b4957ba73..18b98bb0d9 100644 --- a/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBComponent.cs +++ b/Rubberduck.VBEEditor/SafeComWrappers/VBA/VBComponent.cs @@ -56,6 +56,11 @@ public string Name set { Target.Name = value; } } + private string SafeName + { + get { return Path.GetInvalidFileNameChars().Aggregate(Name, (current, c) => current.Replace(c.ToString(), "_")); } + } + public IControls Controls { get @@ -107,7 +112,7 @@ public void Export(string path) /// Destination folder for the resulting source file. public string ExportAsSourceFile(string folder) { - var fullPath = Path.Combine(folder, Name + Type.FileExtension()); + var fullPath = Path.Combine(folder, SafeName + Type.FileExtension()); switch (Type) { case ComponentType.UserForm: @@ -165,7 +170,7 @@ private void ExportDocumentModule(string path) private string ExportToTempFile() { - var path = Path.Combine(Path.GetTempPath(), Name + Type.FileExtension()); + var path = Path.Combine(Path.GetTempPath(), SafeName + Type.FileExtension()); Export(path); return path; } diff --git a/Rubberduck.VBEEditor/Selection.cs b/Rubberduck.VBEEditor/Selection.cs index f761ca0235..188112c2ca 100644 --- a/Rubberduck.VBEEditor/Selection.cs +++ b/Rubberduck.VBEEditor/Selection.cs @@ -2,7 +2,7 @@ namespace Rubberduck.VBEditor { - public struct Selection : IEquatable + public struct Selection : IEquatable, IComparable { public Selection(int startLine, int startColumn, int endLine, int endColumn) { @@ -88,6 +88,20 @@ public bool Equals(Selection other) && other.EndColumn == EndColumn; } + public int CompareTo(Selection other) + { + if (this > other) + { + return 1; + } + if (this < other) + { + return -1; + } + + return 0; + } + public override string ToString() { return (_startLine == _endLine && _startColumn == _endColumn)