Skip to content

Commit d6de269

Browse files
committed
refactored for testability of SCP handler
1 parent 0aaf7c1 commit d6de269

File tree

6 files changed

+240
-131
lines changed

6 files changed

+240
-131
lines changed

Rubberduck.Core/AutoComplete/Service/SelfClosingPair.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1-
namespace Rubberduck.AutoComplete.Service
1+
using Rubberduck.VBEditor;
2+
using System;
3+
4+
namespace Rubberduck.AutoComplete.Service
25
{
3-
public class SelfClosingPair
6+
public class SelfClosingPair : IEquatable<SelfClosingPair>
47
{
8+
[Flags]
9+
public enum MatchType
10+
{
11+
NoMatch = 0,
12+
OpeningCharacterMatch = 1,
13+
ClosingCharacterMatch = 2,
14+
}
15+
516
public SelfClosingPair(char opening, char closing)
617
{
718
OpeningChar = opening;
@@ -15,5 +26,15 @@ public SelfClosingPair(char opening, char closing)
1526
/// True if <see cref="OpeningChar"/> is the same as <see cref="ClosingChar"/>.
1627
/// </summary>
1728
public bool IsSymetric => OpeningChar == ClosingChar;
29+
30+
public bool Equals(SelfClosingPair other) => other?.OpeningChar == OpeningChar &&
31+
other.ClosingChar == ClosingChar;
32+
33+
public override bool Equals(object obj)
34+
{
35+
return obj is SelfClosingPair scp && Equals(scp);
36+
}
37+
38+
public override int GetHashCode() => HashCode.Compute(OpeningChar, ClosingChar);
1839
}
1940
}

Rubberduck.Core/AutoComplete/Service/SelfClosingPairCompletionService.cs

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,25 @@ namespace Rubberduck.AutoComplete.Service
1212
{
1313
public class SelfClosingPairCompletionService
1414
{
15+
/*
16+
// note: this works... but the VBE makes an annoying DING! when the command isn't available.
17+
// todo: implement our own intellisense, then uncomment this code.
1518
private readonly IShowIntelliSenseCommand _showIntelliSense;
1619
1720
public SelfClosingPairCompletionService(IShowIntelliSenseCommand showIntelliSense)
1821
{
1922
_showIntelliSense = showIntelliSense;
2023
}
24+
*/
2125

22-
public CodeString Execute(SelfClosingPair pair, CodeString original, char input)
26+
public bool Execute(SelfClosingPair pair, CodeString original, char input, out CodeString result)
2327
{
28+
result = null;
29+
2430
var previousCharIsClosingChar =
2531
original.CaretPosition.StartColumn > 0 &&
2632
original.CaretLine[original.CaretPosition.StartColumn - 1] == pair.ClosingChar;
33+
2734
var nextCharIsClosingChar =
2835
original.CaretPosition.StartColumn < original.CaretLine.Length &&
2936
original.CaretLine[original.CaretPosition.StartColumn] == pair.ClosingChar;
@@ -33,49 +40,51 @@ public CodeString Execute(SelfClosingPair pair, CodeString original, char input)
3340
previousCharIsClosingChar && !nextCharIsClosingChar
3441
|| original.IsComment || (original.IsInsideStringLiteral && !nextCharIsClosingChar))
3542
{
36-
return null;
43+
return false;
3744
}
3845

3946
if (input == pair.OpeningChar)
4047
{
41-
var result = HandleOpeningChar(pair, original);
42-
return result;
48+
return HandleOpeningChar(pair, original, out result);
4349
}
44-
50+
4551
if (input == pair.ClosingChar)
4652
{
47-
return HandleClosingChar(pair, original);
53+
return HandleClosingChar(pair, original, out result);
4854
}
4955

5056
if (input == '\b')
5157
{
52-
return Execute(pair, original, Keys.Back);
58+
return Execute(pair, original, Keys.Back, out result);
5359
}
5460

55-
return null;
61+
return false;
5662
}
5763

58-
public CodeString Execute(SelfClosingPair pair, CodeString original, Keys input)
64+
public bool Execute(SelfClosingPair pair, CodeString original, Keys input, out CodeString result)
5965
{
66+
result = null;
6067
if (original.IsComment)
6168
{
62-
return null;
69+
// not handling backspace in comments
70+
return false;
6371
}
6472

6573
if (input == Keys.Back)
6674
{
67-
return HandleBackspace(pair, original);
75+
result = HandleBackspace(pair, original);
76+
return true;
6877
}
6978

70-
return null;
79+
return false;
7180
}
7281

73-
private CodeString HandleOpeningChar(SelfClosingPair pair, CodeString original)
82+
private bool HandleOpeningChar(SelfClosingPair pair, CodeString original, out CodeString result)
7483
{
7584
var nextPosition = original.CaretPosition.ShiftRight();
7685
var autoCode = new string(new[] { pair.OpeningChar, pair.ClosingChar });
7786
var lines = original.Lines;
78-
var line = lines[original.CaretPosition.StartLine];
87+
var line = original.CaretLine;
7988

8089
string newCode;
8190
if (string.IsNullOrEmpty(line))
@@ -94,14 +103,17 @@ private CodeString HandleOpeningChar(SelfClosingPair pair, CodeString original)
94103
}
95104
lines[original.CaretPosition.StartLine] = newCode;
96105

97-
return new CodeString(string.Join("\r\n", lines), nextPosition, new Selection(original.SnippetPosition.StartLine, 1, original.SnippetPosition.EndLine, 1));
106+
result = new CodeString(string.Join("\r\n", lines), nextPosition, new Selection(original.SnippetPosition.StartLine, 1, original.SnippetPosition.EndLine, 1));
107+
return true;
98108
}
99109

100-
private CodeString HandleClosingChar(SelfClosingPair pair, CodeString original)
110+
private bool HandleClosingChar(SelfClosingPair pair, CodeString original, out CodeString result)
101111
{
112+
result = null;
102113
if (pair.IsSymetric)
103114
{
104-
return null;
115+
// a symetric pair would have already been handled with the opening character.
116+
return false;
105117
}
106118

107119
var nextIsClosingChar = original.CaretLine.Length > original.CaretCharIndex &&
@@ -111,17 +123,14 @@ private CodeString HandleClosingChar(SelfClosingPair pair, CodeString original)
111123
var nextPosition = original.CaretPosition.ShiftRight();
112124
var newCode = original.Code;
113125

114-
return new CodeString(newCode, nextPosition, new Selection(original.SnippetPosition.StartLine, 1, original.SnippetPosition.EndLine, 1));
126+
result = new CodeString(newCode, nextPosition, new Selection(original.SnippetPosition.StartLine, 1, original.SnippetPosition.EndLine, 1));
127+
return true;
115128
}
116-
return null;
117-
}
118129

119-
private CodeString HandleBackspace(SelfClosingPair pair, CodeString original)
120-
{
121-
return DeleteMatchingTokens(pair, original);
130+
return false;
122131
}
123132

124-
private CodeString DeleteMatchingTokens(SelfClosingPair pair, CodeString original)
133+
private CodeString HandleBackspace(SelfClosingPair pair, CodeString original)
125134
{
126135
var position = original.CaretPosition;
127136
var lines = original.Lines;
Lines changed: 65 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System.Collections.Generic;
2-
using System.Diagnostics;
3-
using System.Windows.Forms;
2+
using System.Linq;
43
using Rubberduck.Settings;
54
using Rubberduck.VBEditor;
65
using Rubberduck.VBEditor.Events;
@@ -10,86 +9,92 @@ namespace Rubberduck.AutoComplete.Service
109
{
1110
public class SelfClosingPairHandler : AutoCompleteHandlerBase
1211
{
13-
private static readonly IEnumerable<SelfClosingPair> SelfClosingPairs = new List<SelfClosingPair>
14-
{
15-
new SelfClosingPair('(', ')'),
16-
new SelfClosingPair('"', '"'),
17-
new SelfClosingPair('[', ']'),
18-
new SelfClosingPair('{', '}'),
19-
};
20-
12+
private readonly IDictionary<char, SelfClosingPair> _selfClosingPairs;
2113
private readonly SelfClosingPairCompletionService _scpService;
2214

2315
public SelfClosingPairHandler(ICodePaneHandler pane, SelfClosingPairCompletionService scpService)
2416
: base(pane)
2517
{
18+
var pairs = new[]
19+
{
20+
new SelfClosingPair('(', ')'),
21+
new SelfClosingPair('"', '"'),
22+
new SelfClosingPair('[', ']'),
23+
new SelfClosingPair('{', '}'),
24+
};
25+
_selfClosingPairs = pairs
26+
.Select(p => new {Key = p.OpeningChar, Pair = p})
27+
.Union(pairs.Where(p => !p.IsSymetric).Select(p => new {Key = p.ClosingChar, Pair = p}))
28+
.ToDictionary(p => p.Key, p => p.Pair);
29+
2630
_scpService = scpService;
2731
}
2832

2933
public override CodeString Handle(AutoCompleteEventArgs e, AutoCompleteSettings settings)
3034
{
35+
if (!_selfClosingPairs.TryGetValue(e.Character, out var pair) && e.Character != '\b')
36+
{
37+
return null;
38+
}
39+
3140
var original = CodePaneHandler.GetCurrentLogicalLine(e.Module);
32-
foreach (var pair in SelfClosingPairs)
41+
if (!HandleInternal(e, original, pair, out var result))
3342
{
34-
var isPresent = original.CaretLine.EndsWith($"{pair.OpeningChar}{pair.ClosingChar}");
35-
36-
var result = ExecuteSelfClosingPair(e, original, pair);
37-
if (result == null)
38-
{
39-
continue;
40-
}
41-
42-
var prettified = CodePaneHandler.Prettify(e.Module, original);
43-
if (!isPresent && original.CaretLine.Length + 2 == prettified.CaretLine.Length &&
44-
prettified.CaretLine.EndsWith($"{pair.OpeningChar}{pair.ClosingChar}"))
45-
{
46-
// prettifier just added the pair for us; likely a Sub or Function statement.
47-
prettified = original; // pretend this didn't happen. note: probably breaks if original has extra whitespace.
48-
}
49-
50-
result = ExecuteSelfClosingPair(e, prettified, pair);
51-
if (result == null)
52-
{
53-
continue;
54-
}
55-
56-
result = CodePaneHandler.Prettify(e.Module, result);
57-
58-
var currentLine = result.Lines[result.CaretPosition.StartLine];
59-
if (!string.IsNullOrWhiteSpace(currentLine) &&
60-
currentLine.EndsWith(" ") &&
61-
result.CaretPosition.StartColumn == currentLine.Length)
62-
{
63-
result = result.ReplaceLine(result.CaretPosition.StartLine, currentLine.TrimEnd());
64-
}
65-
66-
if (pair.OpeningChar == '(' && e.Character != '\b' && !result.CaretLine.EndsWith($"{pair.OpeningChar}{pair.ClosingChar}"))
67-
{
68-
// VBE eats it. just bail out.
69-
return null;
70-
}
71-
72-
e.Handled = true;
73-
result = new CodeString(result.Code, result.CaretPosition, new Selection(result.SnippetPosition.StartLine, 1, result.SnippetPosition.EndLine, 1));
74-
return result;
43+
return null;
7544
}
7645

77-
return null;
46+
var snippetPosition = new Selection(result.SnippetPosition.StartLine, 1, result.SnippetPosition.EndLine, 1);
47+
result = new CodeString(result.Code, result.CaretPosition, snippetPosition);
48+
49+
e.Handled = true;
50+
return result;
7851
}
7952

80-
private CodeString ExecuteSelfClosingPair(AutoCompleteEventArgs e, CodeString original, SelfClosingPair pair)
53+
private bool HandleInternal(AutoCompleteEventArgs e, CodeString original, SelfClosingPair pair, out CodeString result)
8154
{
82-
CodeString result;
83-
if (e.Character == '\b' && original.CaretPosition.StartColumn > 1)
55+
var isPresent = original.CaretLine.EndsWith($"{pair.OpeningChar}{pair.ClosingChar}");
56+
57+
if (!ExecuteSelfClosingPair(e, original, pair, out result))
8458
{
85-
result = _scpService.Execute(pair, original, Keys.Back);
59+
return false;
8660
}
87-
else
61+
62+
var prettified = CodePaneHandler.Prettify(e.Module, original);
63+
if (!isPresent && original.CaretLine.Length + 2 == prettified.CaretLine.Length &&
64+
prettified.CaretLine.EndsWith($"{pair.OpeningChar}{pair.ClosingChar}"))
8865
{
89-
result = _scpService.Execute(pair, original, e.Character);
66+
// prettifier just added the pair for us; likely a Sub or Function statement.
67+
prettified = original; // pretend this didn't happen. note: probably breaks if original has extra whitespace.
9068
}
9169

92-
return result;
70+
if (!ExecuteSelfClosingPair(e, prettified, pair, out result))
71+
{
72+
return false;
73+
}
74+
75+
result = CodePaneHandler.Prettify(e.Module, result);
76+
77+
var currentLine = result.Lines[result.CaretPosition.StartLine];
78+
if (!string.IsNullOrWhiteSpace(currentLine) &&
79+
currentLine.EndsWith(" ") &&
80+
result.CaretPosition.StartColumn == currentLine.Length)
81+
{
82+
result = result.ReplaceLine(result.CaretPosition.StartLine, currentLine.TrimEnd());
83+
}
84+
85+
if (pair.OpeningChar == '(' && e.Character != '\b' &&
86+
!result.CaretLine.EndsWith($"{pair.OpeningChar}{pair.ClosingChar}"))
87+
{
88+
// VBE eats it. just bail out.
89+
return true;
90+
}
91+
92+
return true;
93+
}
94+
95+
private bool ExecuteSelfClosingPair(AutoCompleteEventArgs e, CodeString original, SelfClosingPair pair, out CodeString result)
96+
{
97+
return _scpService.Execute(pair, original, e.Character, out result);
9398
}
9499
}
95100
}

Rubberduck.Core/AutoComplete/Service/ShowIntelliSenseCommand.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ namespace Rubberduck.AutoComplete.Service
77
{
88
public interface IShowIntelliSenseCommand
99
{
10+
/// <summary>
11+
/// WARNING! Makes an utterly annoying DING! in the VBE if the "QuickInfo" command is unavailable.
12+
/// </summary>
1013
void Execute();
1114
}
1215

Rubberduck.Core/Settings/AutoCompleteSettings.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,18 @@ public interface IAutoCompleteSettings
2727
[XmlType(AnonymousType = true)]
2828
public class AutoCompleteSettings : IAutoCompleteSettings, IEquatable<AutoCompleteSettings>
2929
{
30+
public static AutoCompleteSettings AllEnabled =>
31+
new AutoCompleteSettings
32+
{
33+
IsEnabled = true,
34+
BlockCompletion =
35+
new BlockCompletionSettings {IsEnabled = true, CompleteOnEnter = true, CompleteOnTab = true},
36+
SmartConcat =
37+
new SmartConcatSettings {IsEnabled = true, ConcatVbNewLineModifier = ModifierKeySetting.CtrlKey},
38+
SelfClosingPairs =
39+
new SelfClosingPairSettings {IsEnabled = true}
40+
};
41+
3042
public AutoCompleteSettings()
3143
{
3244
SmartConcat = new SmartConcatSettings();

0 commit comments

Comments
 (0)