Skip to content

Commit

Permalink
EditNetSpell marks whole word including underscore
Browse files Browse the repository at this point in the history
  • Loading branch information
mstv committed Nov 7, 2018
1 parent b4a57cf commit 9fe5824
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 16 deletions.
8 changes: 8 additions & 0 deletions GitUI/SpellChecker/EditNetSpell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ protected override void OnRuntimeLoad()
_customUnderlines = new SpellCheckEditControl(TextBox);
TextBox.SelectionChanged += TextBox_SelectionChanged;
TextBox.TextChanged += TextBoxTextChanged;
TextBox.DoubleClick += TextBox_DoubleClick;

EnabledChanged += EditNetSpellEnabledChanged;

Expand Down Expand Up @@ -706,6 +707,13 @@ private void TextBox_SelectionChanged(object sender, EventArgs e)
handler?.Invoke(sender, e);
}

private void TextBox_DoubleClick(object sender, EventArgs e)
{
int cursor = TextBox.GetCharIndexFromPosition(TextBox.PointToClient(MousePosition));
(var start, var length) = _wordAtCursorExtractor.FindWord(TextBox.Text, cursor);
TextBox.Select(start, length);
}

private void ShowWatermark()
{
if (!ContainsFocus && string.IsNullOrEmpty(TextBox.Text) && TextBoxFont != null)
Expand Down
2 changes: 1 addition & 1 deletion GitUI/SpellChecker/SpellCheckerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ internal static class SpellCheckerHelper
{
public static bool IsSeparator(this char c)
{
return !char.IsLetterOrDigit(c);
return !char.IsLetterOrDigit(c) && c != '_';
}
}
}
73 changes: 59 additions & 14 deletions GitUI/SpellChecker/WordAtCursorExtractor.cs
Original file line number Diff line number Diff line change
@@ -1,41 +1,83 @@
using System.Text;
using System;
using System.Text;

namespace GitUI.SpellChecker
{
internal class WordAtCursorExtractor : IWordAtCursorExtractor
{
public string Extract(string text, int cursor)
{
var startIndex = FindStartOfWord(text, cursor);
return startIndex < 0 ? string.Empty : text.Substring(startIndex, cursor - startIndex + 1);
}

public (int start, int length) FindWord(string text, int cursor)
{
int start = Math.Min(FindStartOfWord(text, cursor), cursor);
if (start < 0)
{
return (-1, 0);
}

int end = FindEndOfWord(text, cursor);
return (start, Math.Max(1, end - start));
}

public int FindStartOfWord(string text, int cursor)
{
cursor = Math.Min(cursor, text.Length - 1);
if (cursor < 0)
{
return string.Empty;
return -1;
}

var sb = new StringBuilder();
// do not avoid that the result can be right from the initial cursor position
#if false
if (!BelongsToStartOfWord(text, cursor))
{
return cursor;
}
#endif

while (cursor >= 0)
while (cursor >= 0 && BelongsToStartOfWord(text, cursor))
{
if (text[cursor].IsSeparator() && !IsDot(text[cursor]))
{
break;
}
--cursor;
}

if (IsDot(text[cursor]) && !IsLeadingChar(text, cursor))
{
break;
}
return cursor + 1;
}

sb.Insert(0, text[cursor--]);
public int FindEndOfWord(string text, int cursor)
{
cursor = Math.Min(cursor, text.Length);
if (cursor < 0)
{
return -1;
}

while (cursor < text.Length && BelongsToWord(text[cursor]))
{
++cursor;
}

return sb.ToString();
return cursor;
}

private static bool BelongsToStartOfWord(string text, int cursor)
{
return IsDot(text[cursor]) ? IsLeadingChar(text, cursor) : BelongsToWord(text[cursor]);
}

private static bool IsDot(char c)
{
return c == '.';
}

private static bool BelongsToWord(char c)
{
return !c.IsSeparator();
}

private static bool IsLeadingChar(string text, int cursor)
{
return cursor == 0 || IsSeparatorExceptClosingBrackets(text[cursor - 1]);
Expand All @@ -50,5 +92,8 @@ private static bool IsSeparatorExceptClosingBrackets(char c)
internal interface IWordAtCursorExtractor
{
string Extract(string text, int cursor);
(int start, int length) FindWord(string text, int cursor);
int FindStartOfWord(string text, int cursor);
int FindEndOfWord(string text, int cursor);
}
}
92 changes: 91 additions & 1 deletion UnitTests/GitUITests/SpellChecker/WordAtCursorExtractorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,103 @@ public void SetUp()
[TestCase("Add,git", ExpectedResult = "git")]
[TestCase("Add (.git", ExpectedResult = ".git")]
[TestCase("Introduce Tes", ExpectedResult = "Tes")]
[TestCase("Add).git", ExpectedResult = "git")]
[TestCase("Add).git", ExpectedResult = "git")] // test strings with a closing round bracket confuse the testcase parser a little
[TestCase("[Add].git", ExpectedResult = "git")]
[TestCase("Introduce .babeljs]", ExpectedResult = "")]
[TestCase("func().otherFun", ExpectedResult = "otherFun")]
[TestCase("func().other_fun", ExpectedResult = "other_fun")]
[TestCase("obj._field", ExpectedResult = "_field")]
[TestCase("func(type init_", ExpectedResult = "init_")]
public string ExtractsMeaningfulWord(string text)
{
return _wordAtCursorExtractor.Extract(text, text.Length - 1);
}

[TestCase("", -2, -1, 0, ExpectedResult = true)]
[TestCase("", -1, -1, 0, ExpectedResult = true)]
[TestCase("", 0, -1, 0, ExpectedResult = true)]
[TestCase("", 1, -1, 0, ExpectedResult = true)]
[TestCase("_obj.f", -2, -1, 0, ExpectedResult = true)]
[TestCase("_obj.f", -1, -1, 0, ExpectedResult = true)]
[TestCase("_obj.f", 0, 0, 4, ExpectedResult = true)]
[TestCase("_obj.f", 1, 0, 4, ExpectedResult = true)]
[TestCase("_obj.f", 2, 0, 4, ExpectedResult = true)]
[TestCase("_obj.f", 3, 0, 4, ExpectedResult = true)]
[TestCase("_obj.f", 4, 4, 1, ExpectedResult = true)]
[TestCase("_obj.f", 5, 5, 1, ExpectedResult = true)]
[TestCase("_obj.f", 6, 5, 1, ExpectedResult = true)]
[TestCase("_obj.f", 7, 5, 1, ExpectedResult = true)]
[TestCase("0 3", 0, 0, 1, ExpectedResult = true)]
[TestCase("0 2", 1, 1, 1, ExpectedResult = true)]
[TestCase("0 2", 2, 2, 1, ExpectedResult = true)]
[TestCase("0 2", 3, 3, 1, ExpectedResult = true)]
public bool Test_FindWord(string text, int cursor, int expectedStart, int expectedLength)
{
(var start, var length) = _wordAtCursorExtractor.FindWord(text, cursor);
return start == expectedStart && length == expectedLength;
}

[TestCase("", -2, ExpectedResult = -1)]
[TestCase("", -1, ExpectedResult = -1)]
[TestCase("", 0, ExpectedResult = -1)]
[TestCase("", 1, ExpectedResult = -1)]
[TestCase("012", -2, ExpectedResult = -1)]
[TestCase("012", -1, ExpectedResult = -1)]
[TestCase("012", 0, ExpectedResult = 0)]
[TestCase("012", 1, ExpectedResult = 0)]
[TestCase("012", 2, ExpectedResult = 0)]
[TestCase("012", 3, ExpectedResult = 0)]
[TestCase("012", 4, ExpectedResult = 0)]
[TestCase("01A34", 5, ExpectedResult = 0)]
[TestCase("01a34", 5, ExpectedResult = 0)]
[TestCase("01_34", 5, ExpectedResult = 0)]
[TestCase("01 34", 5, ExpectedResult = 3)]
[TestCase("01.34", 5, ExpectedResult = 3)]
[TestCase("01 .4", 5, ExpectedResult = 3)]
[TestCase("01..4", 5, ExpectedResult = 3)]
[TestCase("01).4", 5, ExpectedResult = 4)]
[TestCase("01].4", 5, ExpectedResult = 4)]
[TestCase("012.4", 5, ExpectedResult = 4)]
[TestCase("012.4", 4, ExpectedResult = 4)]
[TestCase("012.4", 3, ExpectedResult = 4)] // The result is right from the initial position!
[TestCase("012/4", 3, ExpectedResult = 4)] // ditto
[TestCase("012.4", 2, ExpectedResult = 0)]
[TestCase("012.4", 1, ExpectedResult = 0)]
[TestCase("012.4", 0, ExpectedResult = 0)]
public int Test_FindStartOfWord(string text, int cursor)
{
return _wordAtCursorExtractor.FindStartOfWord(text, cursor);
}

[TestCase("", -2, ExpectedResult = -1)]
[TestCase("", -1, ExpectedResult = -1)]
[TestCase("", 0, ExpectedResult = 0)]
[TestCase("", 1, ExpectedResult = 0)]
[TestCase("012", -2, ExpectedResult = -1)]
[TestCase("012", -1, ExpectedResult = -1)]
[TestCase("012", 0, ExpectedResult = 3)]
[TestCase("012", 1, ExpectedResult = 3)]
[TestCase("012", 2, ExpectedResult = 3)]
[TestCase("012", 3, ExpectedResult = 3)]
[TestCase("012", 4, ExpectedResult = 3)]
[TestCase("01A34", 0, ExpectedResult = 5)]
[TestCase("01a34", 0, ExpectedResult = 5)]
[TestCase("01_34", 0, ExpectedResult = 5)]
[TestCase("01 34", 0, ExpectedResult = 2)]
[TestCase("01/34", 0, ExpectedResult = 2)]
[TestCase("01/34", 1, ExpectedResult = 2)]
[TestCase("01/34", 2, ExpectedResult = 2)]
[TestCase("01/34", 3, ExpectedResult = 5)]
[TestCase("01/34", 4, ExpectedResult = 5)]
[TestCase("/12", 0, ExpectedResult = 0)]
[TestCase("/12", 1, ExpectedResult = 3)]
[TestCase("01/", 0, ExpectedResult = 2)]
[TestCase("01/", 1, ExpectedResult = 2)]
[TestCase("01/", 2, ExpectedResult = 2)]
[TestCase("01/", 3, ExpectedResult = 3)]
public int Test_FindEndOfWord(string text, int cursor)
{
return _wordAtCursorExtractor.FindEndOfWord(text, cursor);
}
}
}

0 comments on commit 9fe5824

Please sign in to comment.