Skip to content

Commit dc10dab

Browse files
authored
Merge pull request #4351 from comintern/autocomplete
Sort out keys and chars; fixes "smart concat" kicking in "randomly" when typing a string literal.
2 parents 37639a5 + 5405075 commit dc10dab

File tree

5 files changed

+74
-85
lines changed

5 files changed

+74
-85
lines changed

Rubberduck.Core/AutoComplete/AutoCompleteBlockBase.cs

Lines changed: 35 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,51 +39,49 @@ protected AutoCompleteBlockBase(IConfigProvider<IndenterSettings> indenterSettin
3939

4040
public override bool Execute(AutoCompleteEventArgs e, AutoCompleteSettings settings)
4141
{
42-
var ignoreTab = e.Keys == Keys.Tab && !settings.CompleteBlockOnTab;
43-
var ignoreEnter = e.Keys == Keys.Enter && !settings.CompleteBlockOnEnter;
44-
if (IsInlineCharCompletion || e.Keys == Keys.None || ignoreTab || ignoreEnter)
42+
var ignoreTab = e.Character == '\t' && !settings.CompleteBlockOnTab;
43+
var ignoreEnter = e.Character == '\r' && !settings.CompleteBlockOnEnter;
44+
if (IsInlineCharCompletion || e.IsDelete || ignoreTab || ignoreEnter)
4545
{
4646
return false;
4747
}
4848

49+
var module = e.CodeModule;
50+
using (var pane = module.CodePane)
4951
{
50-
var module = e.CodeModule;
51-
using (var pane = module.CodePane)
52+
var selection = pane.Selection;
53+
var originalCode = module.GetLines(selection);
54+
var code = originalCode.Trim().StripStringLiterals();
55+
var hasComment = code.HasComment(out int commentStart);
56+
57+
var isDeclareStatement = Regex.IsMatch(code, $"\\b{Tokens.Declare}\\b", RegexOptions.IgnoreCase);
58+
var isExitStatement = Regex.IsMatch(code, $"\\b{Tokens.Exit}\\b", RegexOptions.IgnoreCase);
59+
var isNamedArg = Regex.IsMatch(code, $"\\b{InputToken}\\:\\=", RegexOptions.IgnoreCase);
60+
61+
if ((SkipPreCompilerDirective && code.StartsWith("#"))
62+
|| isDeclareStatement || isExitStatement || isNamedArg)
5263
{
53-
var selection = pane.Selection;
54-
var originalCode = module.GetLines(selection);
55-
var code = originalCode.Trim().StripStringLiterals();
56-
var hasComment = code.HasComment(out int commentStart);
57-
58-
var isDeclareStatement = Regex.IsMatch(code, $"\\b{Tokens.Declare}\\b", RegexOptions.IgnoreCase);
59-
var isExitStatement = Regex.IsMatch(code, $"\\b{Tokens.Exit}\\b", RegexOptions.IgnoreCase);
60-
var isNamedArg = Regex.IsMatch(code, $"\\b{InputToken}\\:\\=", RegexOptions.IgnoreCase);
61-
62-
if ((SkipPreCompilerDirective && code.StartsWith("#"))
63-
|| isDeclareStatement || isExitStatement || isNamedArg)
64-
{
65-
return false;
66-
}
67-
68-
if (IsMatch(code) && !IsBlockCompleted(module, selection))
69-
{
70-
var indent = originalCode.TakeWhile(c => char.IsWhiteSpace(c)).Count();
71-
var newCode = OutputToken.PadLeft(OutputToken.Length + indent, ' ');
72-
73-
var stdIndent = IndentBody
74-
? IndenterSettings.Create().IndentSpaces
75-
: 0;
76-
77-
module.InsertLines(selection.NextLine.StartLine, "\n" + newCode);
78-
79-
module.ReplaceLine(selection.NextLine.StartLine, new string(' ', indent + stdIndent));
80-
pane.Selection = new Selection(selection.NextLine.StartLine, indent + stdIndent + 1);
81-
82-
e.Handled = true;
83-
return true;
84-
}
8564
return false;
8665
}
66+
67+
if (IsMatch(code) && !IsBlockCompleted(module, selection))
68+
{
69+
var indent = originalCode.TakeWhile(c => char.IsWhiteSpace(c)).Count();
70+
var newCode = OutputToken.PadLeft(OutputToken.Length + indent, ' ');
71+
72+
var stdIndent = IndentBody
73+
? IndenterSettings.Create().IndentSpaces
74+
: 0;
75+
76+
module.InsertLines(selection.NextLine.StartLine, "\n" + newCode);
77+
78+
module.ReplaceLine(selection.NextLine.StartLine, new string(' ', indent + stdIndent));
79+
pane.Selection = new Selection(selection.NextLine.StartLine, indent + stdIndent + 1);
80+
81+
e.Handled = true;
82+
return true;
83+
}
84+
return false;
8785
}
8886
}
8987

Rubberduck.Core/AutoComplete/AutoCompleteService.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public void ApplyAutoCompleteSettings(Configuration config)
109109

110110
private void HandleKeyDown(object sender, AutoCompleteEventArgs e)
111111
{
112-
if (e.Character == default && e.Keys == Keys.None)
112+
if (e.Character == default && !e.IsDelete)
113113
{
114114
return;
115115
}
@@ -119,7 +119,7 @@ private void HandleKeyDown(object sender, AutoCompleteEventArgs e)
119119
Debug.Assert(qualifiedSelection != null, nameof(qualifiedSelection) + " != null");
120120
var pSelection = qualifiedSelection.Value.Selection;
121121

122-
if (_popupShown || (e.Keys != Keys.None && pSelection.LineCount > 1) || e.Keys.HasFlag(Keys.Delete))
122+
if (_popupShown || pSelection.LineCount > 1 || e.IsDelete)
123123
{
124124
return;
125125
}
@@ -150,9 +150,9 @@ private void HandleSelfClosingPairs(AutoCompleteEventArgs e, ICodeModule module,
150150
foreach (var selfClosingPair in _selfClosingPairs)
151151
{
152152
CodeString result;
153-
if (e.Keys == Keys.Back && pSelection.StartColumn > 1)
153+
if (e.Character == '\b' && pSelection.StartColumn > 1)
154154
{
155-
result = _selfClosingPairCompletion.Execute(selfClosingPair, original, e.Keys);
155+
result = _selfClosingPairCompletion.Execute(selfClosingPair, original, '\b');
156156
}
157157
else
158158
{
@@ -179,7 +179,7 @@ private void HandleSelfClosingPairs(AutoCompleteEventArgs e, ICodeModule module,
179179
private bool HandleSmartConcat(AutoCompleteEventArgs e, Selection pSelection, string currentContent, ICodeModule module)
180180
{
181181
var shouldHandle = _settings.EnableSmartConcat &&
182-
e.Keys.HasFlag(Keys.Enter) &&
182+
e.Character == '\r' &&
183183
IsInsideStringLiteral(pSelection, ref currentContent);
184184

185185
var lastIndexLeftOfCaret = currentContent.Length > 2 ? currentContent.Substring(0, pSelection.StartColumn - 1).LastIndexOf('"') : 0;
@@ -189,7 +189,7 @@ private bool HandleSmartConcat(AutoCompleteEventArgs e, Selection pSelection, st
189189
var whitespace = new string(' ', indent);
190190
var code = $"{currentContent.Substring(0, pSelection.StartColumn - 1)}\" & _\r\n{whitespace}\"{currentContent.Substring(pSelection.StartColumn - 1)}";
191191

192-
if (e.Keys.HasFlag(Keys.Control))
192+
if (e.ControlDown)
193193
{
194194
code = $"{currentContent.Substring(0, pSelection.StartColumn - 1)}\" & vbNewLine & _\r\n{whitespace}\"{currentContent.Substring(pSelection.StartColumn - 1)}";
195195

Rubberduck.VBEEditor/Events/AutoCompleteEventArgs.cs

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System;
2-
using System.Windows.Forms;
32
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
43

54
namespace Rubberduck.VBEditor.Events
@@ -8,20 +7,12 @@ public class AutoCompleteEventArgs : EventArgs
87
{
98
public AutoCompleteEventArgs(ICodeModule module, KeyPressEventArgs e)
109
{
11-
if (e.Key == Keys.Delete ||
12-
e.Key == Keys.Back ||
13-
e.Key.HasFlag(Keys.Enter) ||
14-
e.Key.HasFlag(Keys.Tab))
15-
{
16-
Keys = e.Key;
17-
}
18-
else
19-
{
20-
Character = e.Character;
21-
}
10+
Character = e.Character;
2211
CodeModule = module;
2312
CurrentSelection = module.GetQualifiedSelection().Value.Selection;
2413
CurrentLine = module.GetLines(CurrentSelection);
14+
ControlDown = e.ControlDown;
15+
IsDelete = e.IsDelete;
2516
}
2617

2718
/// <summary>
@@ -36,26 +27,25 @@ public AutoCompleteEventArgs(ICodeModule module, KeyPressEventArgs e)
3627
public ICodeModule CodeModule { get; }
3728

3829
/// <summary>
39-
/// <c>true</c> if the event is originating from a <c>WM_CHAR</c> message.
40-
/// <c>false</c> if the event is originating from a <c>WM_KEYDOWN</c> message.
41-
/// </summary>
42-
/// <remarks>
43-
/// Inline completion is handled on WM_CHAR; deletions and block completion on WM_KEYDOWN.
44-
/// </remarks>
45-
public bool IsCharacter => Keys == default;
46-
/// <summary>
47-
/// The character whose key was pressed. Undefined value if <see cref="Keys"/> isn't `<see cref="Keys.None"/>.
30+
/// The character whose key was pressed (Enter is always '\r'). Default value if Delete was pressed.
4831
/// </summary>
4932
public char Character { get; }
33+
5034
/// <summary>
51-
/// The actionnable key that was pressed. Value is <see cref="Keys.None"/> when <see cref="IsCharacter"/> is <c>true</c>.
35+
/// <c>true</c> if the left control key was down on the keypress.
5236
/// </summary>
53-
public Keys Keys { get; }
37+
public bool ControlDown { get; }
5438

39+
/// <summary>
40+
/// <c>true</c> if the Delete key generated the event.
41+
/// </summary>
42+
public bool IsDelete { get; }
43+
5544
/// <summary>
5645
/// The current location of the caret.
5746
/// </summary>
5847
public Selection CurrentSelection { get; }
48+
5949
/// <summary>
6050
/// The contents of the current line of code.
6151
/// </summary>

Rubberduck.VBEEditor/Events/KeyPressEventArgs.cs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,32 @@ namespace Rubberduck.VBEditor.Events
66
{
77
public class KeyPressEventArgs
88
{
9-
// Note: This offers additional functionality over WindowsApi.KeyPressEventArgs by passing the WndProc arguments.
10-
public KeyPressEventArgs(IntPtr hwnd, IntPtr wParam, IntPtr lParam, char character = default)
9+
public KeyPressEventArgs(IntPtr hwnd, IntPtr wParam, IntPtr lParam, bool keydown = false)
1110
{
1211
Hwnd = hwnd;
1312
WParam = wParam;
1413
LParam = lParam;
15-
Character = character;
16-
if (character == default(char))
14+
ControlDown = (User32.GetKeyState(VirtualKeyStates.VK_CONTROL) & 0x8000) != 0;
15+
16+
if (keydown)
1717
{
18-
Key = (Keys)wParam;
19-
if ((User32.GetKeyState(VirtualKeyStates.VK_CONTROL) & 0x8000) != 0)
20-
{
21-
Key |= Keys.Control;
22-
}
18+
// Why \r and not \n? Because it really doesn't matter...
19+
Character = ((Keys)wParam & Keys.KeyCode) == Keys.Enter? '\r' : default;
2320
}
2421
else
2522
{
26-
IsCharacter = true;
23+
24+
Character = (char)wParam;
2725
}
2826
}
2927

30-
public bool IsCharacter { get; }
3128
public IntPtr Hwnd { get; }
3229
public IntPtr WParam { get; }
3330
public IntPtr LParam { get; }
3431

3532
public bool Handled { get; set; }
36-
33+
public bool IsDelete => (Keys)WParam == Keys.Delete;
3734
public char Character { get; }
38-
public Keys Key { get; }
35+
public bool ControlDown { get; }
3936
}
4037
}

Rubberduck.VBEEditor/WindowsApi/CodePaneSubclass.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,22 @@ public override int SubClassProc(IntPtr hWnd, IntPtr msg, IntPtr wParam, IntPtr
2626
switch ((WM)msg)
2727
{
2828
case WM.CHAR:
29-
var c = (char) wParam;
30-
if (c != '\r' && c != '\n' && c != '\t')
29+
args = new KeyPressEventArgs(hWnd, wParam, lParam);
30+
if (args.Character != '\r' && args.Character != '\n')
3131
{
32-
args = new KeyPressEventArgs(hWnd, wParam, lParam, c);
3332
OnKeyDown(args);
3433
if (args.Handled) { return 0; }
3534
}
3635
break;
3736
case WM.KEYDOWN:
38-
args = new KeyPressEventArgs(hWnd, wParam, lParam);
39-
OnKeyDown(args);
40-
if (args.Handled) { return 0; }
37+
args = new KeyPressEventArgs(hWnd, wParam, lParam, true);
38+
// The only keydown we care about that doesn't generate a WM_CHAR is Delete, and the VBE handles Enter in WM_KEYDOWN,
39+
// so we need to handle it first (otherwise it will already be in code when the managed event is handled).
40+
if (args.IsDelete || args.Character == '\r')
41+
{
42+
OnKeyDown(args);
43+
if (args.Handled) { return 0; }
44+
}
4145
break;
4246
case WM.SETTEXT:
4347
if (!HasValidVbeObject)

0 commit comments

Comments
 (0)