Skip to content

Commit f1b8170

Browse files
committed
Added input validation and reconciliation to HotkeySettings
1 parent 2965ff4 commit f1b8170

File tree

4 files changed

+174
-15
lines changed

4 files changed

+174
-15
lines changed

RetailCoder.VBE/Common/RubberduckHooks.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ public void HookHotkeys()
8787

8888
foreach (var hotkey in settings.Settings.Where(hotkey => hotkey.IsEnabled))
8989
{
90-
AddHook(new Hotkey(_mainWindowHandle, hotkey.ToString(), _mappings[(RubberduckHotkey)Enum.Parse(typeof(RubberduckHotkey), hotkey.Name)]));
90+
RubberduckHotkey assigned;
91+
if (Enum.TryParse(hotkey.Name, out assigned))
92+
{
93+
AddHook(new Hotkey(_mainWindowHandle, hotkey.ToString(), _mappings[assigned]));
94+
}
9195
}
9296
Attach();
9397
}
Lines changed: 80 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
namespace Rubberduck.Settings
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Rubberduck.Common.Hotkeys;
5+
6+
namespace Rubberduck.Settings
27
{
38
public interface IHotkeySettings
49
{
@@ -7,23 +12,84 @@ public interface IHotkeySettings
712

813
public class HotkeySettings : IHotkeySettings
914
{
10-
public HotkeySetting[] Settings { get; set; }
15+
private static readonly HotkeySetting[] Defaults =
16+
{
17+
new HotkeySetting{Name=RubberduckHotkey.ParseAll.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="`" },
18+
new HotkeySetting{Name=RubberduckHotkey.IndentProcedure.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="P" },
19+
new HotkeySetting{Name=RubberduckHotkey.IndentModule.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="M" },
20+
new HotkeySetting{Name=RubberduckHotkey.CodeExplorer.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="R" },
21+
new HotkeySetting{Name=RubberduckHotkey.FindSymbol.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="T" },
22+
new HotkeySetting{Name=RubberduckHotkey.InspectionResults.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="I" },
23+
new HotkeySetting{Name=RubberduckHotkey.TestExplorer.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="T" },
24+
new HotkeySetting{Name=RubberduckHotkey.RefactorMoveCloserToUsage.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="C" },
25+
new HotkeySetting{Name=RubberduckHotkey.RefactorRename.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="R" },
26+
new HotkeySetting{Name=RubberduckHotkey.RefactorExtractMethod.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="M" }
27+
};
28+
29+
private HashSet<HotkeySetting> _settings;
1130

1231
public HotkeySettings()
1332
{
14-
Settings = new[]
33+
Settings = Defaults.ToArray();
34+
}
35+
36+
public HotkeySetting[] Settings
37+
{
38+
get { return _settings.ToArray(); }
39+
set
40+
{
41+
if (value == null || value.Length == 0)
42+
{
43+
_settings = new HashSet<HotkeySetting>(Defaults);
44+
return;
45+
}
46+
_settings = new HashSet<HotkeySetting>();
47+
var incoming = value.ToList();
48+
//Make sure settings are valid to keep trash out of the config file.
49+
RubberduckHotkey assigned;
50+
incoming.RemoveAll(h => !Enum.TryParse(h.Name, out assigned) || !IsValid(h));
51+
52+
//Only take the first setting if multiple definitions are found.
53+
foreach (var setting in incoming.GroupBy(s => s.Name).Select(hotkey => hotkey.First()))
54+
{
55+
//Only allow one hotkey to be enabled with the same key combination.
56+
setting.IsEnabled &= !IsDuplicate(setting);
57+
_settings.Add(setting);
58+
}
59+
60+
//Merge any hotkeys that weren't found in the input.
61+
foreach (var setting in Defaults.Where(setting => _settings.FirstOrDefault(s => s.Name.Equals(setting.Name)) == null))
62+
{
63+
setting.IsEnabled &= !IsDuplicate(setting);
64+
_settings.Add(setting);
65+
}
66+
}
67+
}
68+
69+
private bool IsDuplicate(HotkeySetting candidate)
70+
{
71+
return _settings.FirstOrDefault(
72+
s =>
73+
s.Key1 == candidate.Key1 &&
74+
s.Key2 == candidate.Key2 &&
75+
s.HasAltModifier == candidate.HasAltModifier &&
76+
s.HasCtrlModifier == candidate.HasCtrlModifier &&
77+
s.HasShiftModifier == candidate.HasShiftModifier) != null;
78+
}
79+
80+
private static bool IsValid(HotkeySetting candidate)
81+
{
82+
//This feels a bit sleazy...
83+
try
84+
{
85+
// ReSharper disable once UnusedVariable
86+
var test = new Hotkey(new IntPtr(), candidate.ToString(), null);
87+
return true;
88+
}
89+
catch
1590
{
16-
new HotkeySetting{Name=RubberduckHotkey.ParseAll.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="`" },
17-
new HotkeySetting{Name=RubberduckHotkey.IndentProcedure.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="P" },
18-
new HotkeySetting{Name=RubberduckHotkey.IndentModule.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="M" },
19-
new HotkeySetting{Name=RubberduckHotkey.CodeExplorer.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="R" },
20-
new HotkeySetting{Name=RubberduckHotkey.FindSymbol.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="T" },
21-
new HotkeySetting{Name=RubberduckHotkey.InspectionResults.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="I" },
22-
new HotkeySetting{Name=RubberduckHotkey.TestExplorer.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="T" },
23-
new HotkeySetting{Name=RubberduckHotkey.RefactorMoveCloserToUsage.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="C" },
24-
new HotkeySetting{Name=RubberduckHotkey.RefactorRename.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="R" },
25-
new HotkeySetting{Name=RubberduckHotkey.RefactorExtractMethod.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="M" }
26-
};
91+
return false;
92+
}
2793
}
2894
}
2995
}

RubberduckTests/RubberduckTests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@
140140
<Compile Include="Refactoring\RemoveParametersTests.cs" />
141141
<Compile Include="Refactoring\RenameTests.cs" />
142142
<Compile Include="Refactoring\ReorderParametersTests.cs" />
143+
<Compile Include="Settings\HotkeySettingsTests.cs" />
143144
<Compile Include="Settings\InspectionSettingsTests.cs" />
144145
<Compile Include="Settings\GeneralSettingsTests.cs" />
145146
<Compile Include="Settings\IndenterSettingsTests.cs" />
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
using System.Linq;
2+
using Microsoft.VisualStudio.TestTools.UnitTesting;
3+
using Rubberduck.Settings;
4+
5+
namespace RubberduckTests.Settings
6+
{
7+
[TestClass]
8+
public class HotkeySettingsTests
9+
{
10+
[TestMethod]
11+
public void DefaultsSetInCtor()
12+
{
13+
var expected = new[]
14+
{
15+
new HotkeySetting{Name=RubberduckHotkey.ParseAll.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="`" },
16+
new HotkeySetting{Name=RubberduckHotkey.IndentProcedure.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="P" },
17+
new HotkeySetting{Name=RubberduckHotkey.IndentModule.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="M" },
18+
new HotkeySetting{Name=RubberduckHotkey.CodeExplorer.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="R" },
19+
new HotkeySetting{Name=RubberduckHotkey.FindSymbol.ToString(), IsEnabled=true, HasCtrlModifier = true, Key1="T" },
20+
new HotkeySetting{Name=RubberduckHotkey.InspectionResults.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="I" },
21+
new HotkeySetting{Name=RubberduckHotkey.TestExplorer.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="T" },
22+
new HotkeySetting{Name=RubberduckHotkey.RefactorMoveCloserToUsage.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="C" },
23+
new HotkeySetting{Name=RubberduckHotkey.RefactorRename.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="R" },
24+
new HotkeySetting{Name=RubberduckHotkey.RefactorExtractMethod.ToString(), IsEnabled=true, HasCtrlModifier = true, HasShiftModifier = true, Key1="M" }
25+
};
26+
27+
var settings = new HotkeySettings();
28+
var actual = settings.Settings;
29+
30+
Assert.IsTrue(expected.SequenceEqual(actual));
31+
}
32+
33+
[TestMethod]
34+
public void InvalidSettingNameWontAdd()
35+
{
36+
var settings = new HotkeySettings();
37+
var expected = settings.Settings;
38+
39+
settings.Settings = new[] {new HotkeySetting {Name = "Foobar", IsEnabled = false, Key1 = "CTRL-C"}};
40+
41+
var actual = settings.Settings;
42+
43+
Assert.IsTrue(expected.SequenceEqual(actual));
44+
}
45+
46+
[TestMethod]
47+
public void InvalidSettingKeyWontAdd()
48+
{
49+
var settings = new HotkeySettings();
50+
var expected = settings.Settings;
51+
52+
settings.Settings = new[] { new HotkeySetting { Name = "ParseAll", IsEnabled = false, Key1 = "Foobar" } };
53+
54+
var actual = settings.Settings;
55+
56+
Assert.IsTrue(expected.SequenceEqual(actual));
57+
}
58+
59+
[TestMethod]
60+
public void DuplicateKeysAreDeactivated()
61+
{
62+
var duplicate1 = new HotkeySetting {Name = "ParseAll", IsEnabled = true, Key1 = "X"};
63+
var duplicate2 = new HotkeySetting {Name = "FindSymbol", IsEnabled = true, Key1 = "X"};
64+
65+
// ReSharper disable once UnusedVariable
66+
var settings = new HotkeySettings
67+
{
68+
Settings = new[] {duplicate1, duplicate2}
69+
};
70+
71+
Assert.IsFalse(duplicate1.IsEnabled == duplicate2.IsEnabled);
72+
}
73+
74+
[TestMethod]
75+
public void DuplicateNamesAreIgnored()
76+
{
77+
var expected = new HotkeySetting { Name = "ParseAll", IsEnabled = true, Key1 = "X" };
78+
var duplicate = new HotkeySetting { Name = "ParseAll", IsEnabled = true, Key1 = "Y" };
79+
80+
var settings = new HotkeySettings
81+
{
82+
Settings = new[] { expected, duplicate }
83+
};
84+
85+
Assert.IsFalse(settings.Settings.Contains(duplicate));
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)