mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-28 00:38:00 +01:00
Merge branch 'develop' into fixes_2336_ignore_border
This commit is contained in:
@@ -324,6 +324,7 @@ namespace Terminal.Gui {
|
||||
if (IsWordChar ((char)kb.Key)) {
|
||||
Visible = true;
|
||||
closed = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kb.Key == Reopen) {
|
||||
@@ -332,6 +333,9 @@ namespace Terminal.Gui {
|
||||
|
||||
if (closed || Suggestions.Count == 0) {
|
||||
Visible = false;
|
||||
if (!closed) {
|
||||
Close ();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -345,6 +349,17 @@ namespace Terminal.Gui {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (kb.Key == Key.CursorLeft || kb.Key == Key.CursorRight) {
|
||||
GenerateSuggestions (kb.Key == Key.CursorLeft ? -1 : 1);
|
||||
if (Suggestions.Count == 0) {
|
||||
Visible = false;
|
||||
if (!closed) {
|
||||
Close ();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kb.Key == SelectionKey) {
|
||||
return Select ();
|
||||
}
|
||||
@@ -368,6 +383,9 @@ namespace Terminal.Gui {
|
||||
public virtual bool MouseEvent (MouseEvent me, bool fromHost = false)
|
||||
{
|
||||
if (fromHost) {
|
||||
if (!Visible) {
|
||||
return false;
|
||||
}
|
||||
GenerateSuggestions ();
|
||||
if (Visible && Suggestions.Count == 0) {
|
||||
Visible = false;
|
||||
@@ -444,7 +462,8 @@ namespace Terminal.Gui {
|
||||
/// Populates <see cref="Suggestions"/> with all strings in <see cref="AllSuggestions"/> that
|
||||
/// match with the current cursor position/text in the <see cref="HostControl"/>
|
||||
/// </summary>
|
||||
public virtual void GenerateSuggestions ()
|
||||
/// <param name="columnOffset">The column offset.</param>
|
||||
public virtual void GenerateSuggestions (int columnOffset = 0)
|
||||
{
|
||||
// if there is nothing to pick from
|
||||
if (AllSuggestions.Count == 0) {
|
||||
@@ -452,7 +471,7 @@ namespace Terminal.Gui {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentWord = GetCurrentWord ();
|
||||
var currentWord = GetCurrentWord (columnOffset);
|
||||
|
||||
if (string.IsNullOrWhiteSpace (currentWord)) {
|
||||
ClearSuggestions ();
|
||||
@@ -524,11 +543,12 @@ namespace Terminal.Gui {
|
||||
/// <summary>
|
||||
/// Returns the currently selected word from the <see cref="HostControl"/>.
|
||||
/// <para>
|
||||
/// When overriding this method views can make use of <see cref="IdxToWord(List{Rune}, int)"/>
|
||||
/// When overriding this method views can make use of <see cref="IdxToWord(List{Rune}, int, int)"/>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="columnOffset">The column offset.</param>
|
||||
/// <returns></returns>
|
||||
protected abstract string GetCurrentWord ();
|
||||
protected abstract string GetCurrentWord (int columnOffset = 0);
|
||||
|
||||
/// <summary>
|
||||
/// <para>
|
||||
@@ -536,37 +556,40 @@ namespace Terminal.Gui {
|
||||
/// or null. Also returns null if the <paramref name="idx"/> is positioned in the middle of a word.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>Use this method to determine whether autocomplete should be shown when the cursor is at
|
||||
/// a given point in a line and to get the word from which suggestions should be generated.</para>
|
||||
/// <para>
|
||||
/// Use this method to determine whether autocomplete should be shown when the cursor is at
|
||||
/// a given point in a line and to get the word from which suggestions should be generated.
|
||||
/// Use the <paramref name="columnOffset"/> to indicate if search the word at left (negative),
|
||||
/// at right (positive) or at the current column (zero) which is the default.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="line"></param>
|
||||
/// <param name="idx"></param>
|
||||
/// <param name="columnOffset"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual string IdxToWord (List<Rune> line, int idx)
|
||||
protected virtual string IdxToWord (List<Rune> line, int idx, int columnOffset = 0)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder ();
|
||||
var endIdx = idx;
|
||||
|
||||
// do not generate suggestions if the cursor is positioned in the middle of a word
|
||||
bool areMidWord;
|
||||
|
||||
if (idx == line.Count) {
|
||||
// the cursor positioned at the very end of the line
|
||||
areMidWord = false;
|
||||
} else {
|
||||
// we are in the middle of a word if the cursor is over a letter/number
|
||||
areMidWord = IsWordChar (line [idx]);
|
||||
// get the ending word index
|
||||
while (endIdx < line.Count) {
|
||||
if (IsWordChar (line [endIdx])) {
|
||||
endIdx++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// if we are in the middle of a word then there is no way to autocomplete that word
|
||||
if (areMidWord) {
|
||||
// It isn't a word char then there is no way to autocomplete that word
|
||||
if (endIdx == idx && columnOffset != 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// we are at the end of a word. Work out what has been typed so far
|
||||
while (idx-- > 0) {
|
||||
|
||||
if (IsWordChar (line [idx])) {
|
||||
sb.Insert (0, (char)line [idx]);
|
||||
while (endIdx-- > 0) {
|
||||
if (IsWordChar (line [endIdx])) {
|
||||
sb.Insert (0, (char)line [endIdx]);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ namespace Terminal.Gui {
|
||||
/// Populates <see cref="Suggestions"/> with all strings in <see cref="AllSuggestions"/> that
|
||||
/// match with the current cursor position/text in the <see cref="HostControl"/>.
|
||||
/// </summary>
|
||||
void GenerateSuggestions ();
|
||||
/// <param name="columnOffset">The column offset. Current (zero - default), left (negative), right (positive).</param>
|
||||
void GenerateSuggestions (int columnOffset = 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1346,12 +1346,12 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetCurrentWord ()
|
||||
protected override string GetCurrentWord (int columnOffset = 0)
|
||||
{
|
||||
var host = (TextField)HostControl;
|
||||
var currentLine = host.Text.ToRuneList ();
|
||||
var cursorPosition = Math.Min (host.CursorPosition, currentLine.Count);
|
||||
return IdxToWord (currentLine, cursorPosition);
|
||||
var cursorPosition = Math.Min (host.CursorPosition + columnOffset, currentLine.Count);
|
||||
return IdxToWord (currentLine, cursorPosition, columnOffset);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -2447,6 +2447,10 @@ namespace Terminal.Gui {
|
||||
|
||||
PositionCursor ();
|
||||
|
||||
if (clickWithSelecting) {
|
||||
clickWithSelecting = false;
|
||||
return;
|
||||
}
|
||||
if (SelectedLength > 0)
|
||||
return;
|
||||
|
||||
@@ -2677,8 +2681,10 @@ namespace Terminal.Gui {
|
||||
need = true;
|
||||
} else if ((wordWrap && leftColumn > 0) || (dSize.size + RightOffset < Frame.Width + offB.width
|
||||
&& tSize.size + RightOffset < Frame.Width + offB.width)) {
|
||||
leftColumn = 0;
|
||||
need = true;
|
||||
if (leftColumn > 0) {
|
||||
leftColumn = 0;
|
||||
need = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentRow < topRow) {
|
||||
@@ -4279,6 +4285,7 @@ namespace Terminal.Gui {
|
||||
}
|
||||
|
||||
bool isButtonShift;
|
||||
bool clickWithSelecting;
|
||||
|
||||
///<inheritdoc/>
|
||||
public override bool MouseEvent (MouseEvent ev)
|
||||
@@ -4372,6 +4379,7 @@ namespace Terminal.Gui {
|
||||
columnTrack = currentColumn;
|
||||
} else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed)) {
|
||||
if (shiftSelecting) {
|
||||
clickWithSelecting = true;
|
||||
StopSelecting ();
|
||||
}
|
||||
ProcessMouseClick (ev, out _);
|
||||
@@ -4475,12 +4483,12 @@ namespace Terminal.Gui {
|
||||
public class TextViewAutocomplete : Autocomplete {
|
||||
|
||||
///<inheritdoc/>
|
||||
protected override string GetCurrentWord ()
|
||||
protected override string GetCurrentWord (int columnOffset = 0)
|
||||
{
|
||||
var host = (TextView)HostControl;
|
||||
var currentLine = host.GetCurrentLine ();
|
||||
var cursorPosition = Math.Min (host.CurrentColumn, currentLine.Count);
|
||||
return IdxToWord (currentLine, cursorPosition);
|
||||
var cursorPosition = Math.Min (host.CurrentColumn + columnOffset, currentLine.Count);
|
||||
return IdxToWord (currentLine, cursorPosition, columnOffset);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
||||
@@ -6,9 +6,16 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Terminal.Gui;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Terminal.Gui.ViewTests {
|
||||
public class AutocompleteTests {
|
||||
readonly ITestOutputHelper output;
|
||||
|
||||
public AutocompleteTests (ITestOutputHelper output)
|
||||
{
|
||||
this.output = output;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_GenerateSuggestions_Simple ()
|
||||
@@ -151,5 +158,84 @@ namespace Terminal.Gui.ViewTests {
|
||||
Assert.Empty (tv.Autocomplete.Suggestions);
|
||||
Assert.Equal (3, tv.Autocomplete.AllSuggestions.Count);
|
||||
}
|
||||
|
||||
[Fact, AutoInitShutdown]
|
||||
public void CursorLeft_CursorRight_Mouse_Button_Pressed_Does_Not_Show_Popup ()
|
||||
{
|
||||
var tv = new TextView () {
|
||||
Width = 50,
|
||||
Height = 5,
|
||||
Text = "This a long line and against TextView."
|
||||
};
|
||||
tv.Autocomplete.AllSuggestions = Regex.Matches (tv.Text.ToString (), "\\w+")
|
||||
.Select (s => s.Value)
|
||||
.Distinct ().ToList ();
|
||||
var top = Application.Top;
|
||||
top.Add (tv);
|
||||
Application.Begin (top);
|
||||
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
|
||||
Application.Refresh ();
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (@"
|
||||
This a long line and against TextView.", output);
|
||||
}
|
||||
|
||||
Assert.True (tv.MouseEvent (new MouseEvent () {
|
||||
X = 6,
|
||||
Y = 0,
|
||||
Flags = MouseFlags.Button1Pressed
|
||||
}));
|
||||
Application.Refresh ();
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (@"
|
||||
This a long line and against TextView.", output);
|
||||
|
||||
Assert.True (tv.ProcessKey (new KeyEvent (Key.g, new KeyModifiers ())));
|
||||
Application.Refresh ();
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (@"
|
||||
This ag long line and against TextView.
|
||||
against ", output);
|
||||
|
||||
Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
|
||||
Application.Refresh ();
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (@"
|
||||
This ag long line and against TextView.
|
||||
against ", output);
|
||||
|
||||
Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
|
||||
Application.Refresh ();
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (@"
|
||||
This ag long line and against TextView.
|
||||
against ", output);
|
||||
|
||||
Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
|
||||
Application.Refresh ();
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (@"
|
||||
This ag long line and against TextView.", output);
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
|
||||
Application.Refresh ();
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (@"
|
||||
This ag long line and against TextView.", output);
|
||||
}
|
||||
|
||||
Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
|
||||
Application.Refresh ();
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (@"
|
||||
This a long line and against TextView.", output);
|
||||
|
||||
Assert.True (tv.ProcessKey (new KeyEvent (Key.n, new KeyModifiers ())));
|
||||
Application.Refresh ();
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (@"
|
||||
This an long line and against TextView.
|
||||
and ", output);
|
||||
|
||||
Assert.True (tv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
|
||||
Application.Refresh ();
|
||||
TestHelpers.AssertDriverContentsWithFrameAre (@"
|
||||
This an long line and against TextView.", output);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user