mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* Move parallelizable to new file * Add UseSameRuneTypeForWords property * Add SelectWordOnlyOnDoubleClick property and ProcessDoubleClickSelection method * Change IsSameRuneType method to also handle equivalent rune types * Fix WordBackward and WordForward to support properly handle rune types * Fix unit test to deal properly with the new roles of rune types * Add new unit tests * Remove duplicated unit test * Add UseSameRuneTypeForWords and SelectWordOnlyOnDoubleClick handling into Editor scenario
This commit is contained in:
@@ -201,6 +201,8 @@ public class Editor : Scenario
|
|||||||
CreateAutocomplete (),
|
CreateAutocomplete (),
|
||||||
CreateAllowsTabChecked (),
|
CreateAllowsTabChecked (),
|
||||||
CreateReadOnlyChecked (),
|
CreateReadOnlyChecked (),
|
||||||
|
CreateUseSameRuneTypeForWords (),
|
||||||
|
CreateSelectWordOnlyOnDoubleClick (),
|
||||||
new MenuItem (
|
new MenuItem (
|
||||||
"Colors",
|
"Colors",
|
||||||
"",
|
"",
|
||||||
@@ -776,6 +778,26 @@ public class Editor : Scenario
|
|||||||
return new [] { item };
|
return new [] { item };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private MenuItem CreateSelectWordOnlyOnDoubleClick ()
|
||||||
|
{
|
||||||
|
var item = new MenuItem { Title = "SelectWordOnlyOnDoubleClick" };
|
||||||
|
item.CheckType |= MenuItemCheckStyle.Checked;
|
||||||
|
item.Checked = _textView.SelectWordOnlyOnDoubleClick;
|
||||||
|
item.Action += () => _textView.SelectWordOnlyOnDoubleClick = (bool)(item.Checked = !item.Checked);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MenuItem CreateUseSameRuneTypeForWords ()
|
||||||
|
{
|
||||||
|
var item = new MenuItem { Title = "UseSameRuneTypeForWords" };
|
||||||
|
item.CheckType |= MenuItemCheckStyle.Checked;
|
||||||
|
item.Checked = _textView.UseSameRuneTypeForWords;
|
||||||
|
item.Action += () => _textView.UseSameRuneTypeForWords = (bool)(item.Checked = !item.Checked);
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
private MenuItem CreateReadOnlyChecked ()
|
private MenuItem CreateReadOnlyChecked ()
|
||||||
{
|
{
|
||||||
var item = new MenuItem { Title = "Read Only" };
|
var item = new MenuItem { Title = "Read Only" };
|
||||||
|
|||||||
@@ -570,6 +570,19 @@ public class TextField : View, IDesignable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Used { get; set; }
|
public bool Used { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether the word forward and word backward navigation should use the same or equivalent rune type.
|
||||||
|
/// Default is <c>false</c> meaning using equivalent rune type.
|
||||||
|
/// </summary>
|
||||||
|
public bool UseSameRuneTypeForWords { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether the word navigation should select only the word itself without spaces around it or with the
|
||||||
|
/// spaces at right.
|
||||||
|
/// Default is <c>false</c> meaning that the spaces at right are included in the selection.
|
||||||
|
/// </summary>
|
||||||
|
public bool SelectWordOnlyOnDoubleClick { get; set; }
|
||||||
|
|
||||||
/// <summary>Clear the selected text.</summary>
|
/// <summary>Clear the selected text.</summary>
|
||||||
public void ClearAllSelection ()
|
public void ClearAllSelection ()
|
||||||
{
|
{
|
||||||
@@ -754,7 +767,7 @@ public class TextField : View, IDesignable
|
|||||||
public virtual void KillWordBackwards ()
|
public virtual void KillWordBackwards ()
|
||||||
{
|
{
|
||||||
ClearAllSelection ();
|
ClearAllSelection ();
|
||||||
(int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0);
|
(int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0, UseSameRuneTypeForWords);
|
||||||
|
|
||||||
if (newPos is null)
|
if (newPos is null)
|
||||||
{
|
{
|
||||||
@@ -777,7 +790,7 @@ public class TextField : View, IDesignable
|
|||||||
public virtual void KillWordForwards ()
|
public virtual void KillWordForwards ()
|
||||||
{
|
{
|
||||||
ClearAllSelection ();
|
ClearAllSelection ();
|
||||||
(int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0);
|
(int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0, UseSameRuneTypeForWords);
|
||||||
|
|
||||||
if (newPos is null)
|
if (newPos is null)
|
||||||
{
|
{
|
||||||
@@ -857,43 +870,15 @@ public class TextField : View, IDesignable
|
|||||||
{
|
{
|
||||||
EnsureHasFocus ();
|
EnsureHasFocus ();
|
||||||
int x = PositionCursor (ev);
|
int x = PositionCursor (ev);
|
||||||
int sbw = x;
|
(int startCol, int col, int row)? newPos = GetModel ().ProcessDoubleClickSelection (x, x, 0, UseSameRuneTypeForWords, SelectWordOnlyOnDoubleClick);
|
||||||
|
|
||||||
if (x == _text.Count
|
if (newPos is null)
|
||||||
|| (x > 0 && (char)_text [x - 1].Value != ' ')
|
|
||||||
|| (x > 0 && (char)_text [x].Value == ' '))
|
|
||||||
{
|
|
||||||
(int col, int row)? newPosBw = GetModel ().WordBackward (x, 0);
|
|
||||||
|
|
||||||
if (newPosBw is null)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
sbw = newPosBw.Value.col;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sbw != -1)
|
|
||||||
{
|
|
||||||
x = sbw;
|
|
||||||
PositionCursor (x);
|
|
||||||
}
|
|
||||||
|
|
||||||
(int col, int row)? newPosFw = GetModel ().WordForward (x, 0);
|
|
||||||
|
|
||||||
if (newPosFw is null)
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClearAllSelection ();
|
SelectedStart = newPos.Value.startCol;
|
||||||
|
CursorPosition = newPos.Value.col;
|
||||||
if (newPosFw.Value.col != -1 && sbw != -1)
|
|
||||||
{
|
|
||||||
_cursorPosition = newPosFw.Value.col;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrepareSelection (sbw, newPosFw.Value.col - sbw);
|
|
||||||
}
|
}
|
||||||
else if (ev.Flags == MouseFlags.Button1TripleClicked)
|
else if (ev.Flags == MouseFlags.Button1TripleClicked)
|
||||||
{
|
{
|
||||||
@@ -1502,7 +1487,7 @@ public class TextField : View, IDesignable
|
|||||||
private void MoveWordLeft ()
|
private void MoveWordLeft ()
|
||||||
{
|
{
|
||||||
ClearAllSelection ();
|
ClearAllSelection ();
|
||||||
(int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0);
|
(int col, int row)? newPos = GetModel ().WordBackward (_cursorPosition, 0, UseSameRuneTypeForWords);
|
||||||
|
|
||||||
if (newPos is null)
|
if (newPos is null)
|
||||||
{
|
{
|
||||||
@@ -1528,7 +1513,7 @@ public class TextField : View, IDesignable
|
|||||||
|
|
||||||
if (x > 0)
|
if (x > 0)
|
||||||
{
|
{
|
||||||
(int col, int row)? newPos = GetModel ().WordBackward (x, 0);
|
(int col, int row)? newPos = GetModel ().WordBackward (x, 0, UseSameRuneTypeForWords);
|
||||||
|
|
||||||
if (newPos is null)
|
if (newPos is null)
|
||||||
{
|
{
|
||||||
@@ -1548,7 +1533,7 @@ public class TextField : View, IDesignable
|
|||||||
private void MoveWordRight ()
|
private void MoveWordRight ()
|
||||||
{
|
{
|
||||||
ClearAllSelection ();
|
ClearAllSelection ();
|
||||||
(int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0);
|
(int col, int row)? newPos = GetModel ().WordForward (_cursorPosition, 0, UseSameRuneTypeForWords);
|
||||||
|
|
||||||
if (newPos is null)
|
if (newPos is null)
|
||||||
{
|
{
|
||||||
@@ -1568,7 +1553,7 @@ public class TextField : View, IDesignable
|
|||||||
if (_cursorPosition < _text.Count)
|
if (_cursorPosition < _text.Count)
|
||||||
{
|
{
|
||||||
int x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
|
int x = _start > -1 && _start > _cursorPosition ? _start : _cursorPosition;
|
||||||
(int col, int row)? newPos = GetModel ().WordForward (x, 0);
|
(int col, int row)? newPos = GetModel ().WordForward (x, 0, UseSameRuneTypeForWords);
|
||||||
|
|
||||||
if (newPos is null)
|
if (newPos is null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ internal class TextModel
|
|||||||
return sb.ToString ();
|
return sb.ToString ();
|
||||||
}
|
}
|
||||||
|
|
||||||
public (int col, int row)? WordBackward (int fromCol, int fromRow)
|
public (int col, int row)? WordBackward (int fromCol, int fromRow, bool useSameRuneType)
|
||||||
{
|
{
|
||||||
if (fromRow == 0 && fromCol == 0)
|
if (fromRow == 0 && fromCol == 0)
|
||||||
{
|
{
|
||||||
@@ -245,7 +245,7 @@ internal class TextModel
|
|||||||
|
|
||||||
RuneType runeType = GetRuneType (rune);
|
RuneType runeType = GetRuneType (rune);
|
||||||
|
|
||||||
int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
|
int lastValidCol = IsSameRuneType (rune, runeType, useSameRuneType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
|
||||||
? col
|
? col
|
||||||
: -1;
|
: -1;
|
||||||
|
|
||||||
@@ -253,21 +253,29 @@ internal class TextModel
|
|||||||
{
|
{
|
||||||
if (Rune.IsWhiteSpace (nRune))
|
if (Rune.IsWhiteSpace (nRune))
|
||||||
{
|
{
|
||||||
while (MovePrev (ref nCol, ref nRow, out nRune))
|
while (MovePrev (ref nCol, ref nRow, out nRune, useSameRuneType))
|
||||||
{
|
{
|
||||||
|
lastValidCol = nCol;
|
||||||
|
|
||||||
if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))
|
if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))
|
||||||
{
|
{
|
||||||
lastValidCol = nCol;
|
rune = nRune;
|
||||||
|
runeType = GetRuneType (nRune);
|
||||||
if (runeType == RuneType.IsWhiteSpace || runeType == RuneType.IsUnknown)
|
|
||||||
{
|
|
||||||
runeType = GetRuneType (nRune);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (lastValidCol > -1)
|
||||||
|
{
|
||||||
|
nCol = lastValidCol;
|
||||||
|
nRow = fromRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!Rune.IsWhiteSpace (nRune) && Rune.IsWhiteSpace (rune))
|
||||||
|
|| (Rune.IsWhiteSpace (nRune) && !Rune.IsWhiteSpace (rune)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)))
|
if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)))
|
||||||
{
|
{
|
||||||
List<Cell> line = GetLine (nRow);
|
List<Cell> line = GetLine (nRow);
|
||||||
@@ -276,38 +284,18 @@ internal class TextModel
|
|||||||
{
|
{
|
||||||
nCol = lastValidCol + Math.Max (lastValidCol, line.Count);
|
nCol = lastValidCol + Math.Max (lastValidCol, line.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (MovePrev (ref nCol, ref nRow, out nRune))
|
|
||||||
{
|
|
||||||
if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune) && !Rune.IsSymbol (nRune))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nRow != fromRow)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastValidCol =
|
|
||||||
(IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune)) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)
|
|
||||||
? nCol
|
|
||||||
: lastValidCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastValidCol > -1)
|
|
||||||
{
|
|
||||||
nCol = lastValidCol;
|
|
||||||
nRow = fromRow;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!MovePrev (ref nCol, ref nRow, out nRune))
|
if (!MovePrev (ref nCol, ref nRow, out nRune, useSameRuneType))
|
||||||
{
|
{
|
||||||
|
if (lastValidCol > -1)
|
||||||
|
{
|
||||||
|
nCol = lastValidCol;
|
||||||
|
nRow = fromRow;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +309,7 @@ internal class TextModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
lastValidCol =
|
lastValidCol =
|
||||||
(IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune)) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)
|
(IsSameRuneType (nRune, runeType, useSameRuneType) && Rune.IsLetterOrDigit (nRune)) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)
|
||||||
? nCol
|
? nCol
|
||||||
: lastValidCol;
|
: lastValidCol;
|
||||||
|
|
||||||
@@ -350,6 +338,15 @@ internal class TextModel
|
|||||||
return (col, row);
|
return (col, row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fromCol == col && fromRow == row && row > 0)
|
||||||
|
{
|
||||||
|
row--;
|
||||||
|
List<Cell> line = GetLine (row);
|
||||||
|
col = line.Count;
|
||||||
|
|
||||||
|
return (col, row);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@@ -358,7 +355,7 @@ internal class TextModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public (int col, int row)? WordForward (int fromCol, int fromRow)
|
public (int col, int row)? WordForward (int fromCol, int fromRow, bool useSameRuneType)
|
||||||
{
|
{
|
||||||
if (fromRow == _lines.Count - 1 && fromCol == GetLine (_lines.Count - 1).Count)
|
if (fromRow == _lines.Count - 1 && fromCol == GetLine (_lines.Count - 1).Count)
|
||||||
{
|
{
|
||||||
@@ -370,10 +367,10 @@ internal class TextModel
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Rune rune = RuneAt (col, row)!.Value.Rune;
|
Rune rune = _lines [row].Count > 0 ? RuneAt (col, row)!.Value.Rune : default (Rune);
|
||||||
RuneType runeType = GetRuneType (rune);
|
RuneType runeType = GetRuneType (rune);
|
||||||
|
|
||||||
int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
|
int lastValidCol = IsSameRuneType (rune, runeType, useSameRuneType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
|
||||||
? col
|
? col
|
||||||
: -1;
|
: -1;
|
||||||
|
|
||||||
@@ -381,16 +378,23 @@ internal class TextModel
|
|||||||
{
|
{
|
||||||
if (Rune.IsWhiteSpace (nRune))
|
if (Rune.IsWhiteSpace (nRune))
|
||||||
{
|
{
|
||||||
while (MoveNext (ref nCol, ref nRow, out nRune))
|
while (MoveNext (ref nCol, ref nRow, out nRune, useSameRuneType))
|
||||||
{
|
{
|
||||||
|
lastValidCol = nCol;
|
||||||
|
|
||||||
if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))
|
if (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune))
|
||||||
{
|
{
|
||||||
lastValidCol = nCol;
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastValidCol = nCol;
|
||||||
|
|
||||||
|
if (!Rune.IsWhiteSpace (nRune) && Rune.IsWhiteSpace (rune))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)))
|
if (nRow != fromRow && (Rune.IsLetterOrDigit (nRune) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)))
|
||||||
{
|
{
|
||||||
if (lastValidCol > -1)
|
if (lastValidCol > -1)
|
||||||
@@ -401,24 +405,6 @@ internal class TextModel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (MoveNext (ref nCol, ref nRow, out nRune))
|
|
||||||
{
|
|
||||||
if (!Rune.IsLetterOrDigit (nRune) && !Rune.IsPunctuation (nRune) && !Rune.IsSymbol (nRune))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nRow != fromRow)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastValidCol =
|
|
||||||
(IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune)) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)
|
|
||||||
? nCol
|
|
||||||
: lastValidCol;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastValidCol > -1)
|
if (lastValidCol > -1)
|
||||||
{
|
{
|
||||||
nCol = lastValidCol;
|
nCol = lastValidCol;
|
||||||
@@ -427,12 +413,14 @@ internal class TextModel
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!MoveNext (ref nCol, ref nRow, out nRune))
|
if (!MoveNext (ref nCol, ref nRow, out nRune, useSameRuneType))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsSameRuneType (nRune, runeType) && !Rune.IsWhiteSpace (nRune))
|
lastValidCol = nCol;
|
||||||
|
|
||||||
|
if (!IsSameRuneType (nRune, runeType, useSameRuneType) && !Rune.IsWhiteSpace (nRune))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -446,11 +434,6 @@ internal class TextModel
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastValidCol =
|
|
||||||
(IsSameRuneType (nRune, runeType) && Rune.IsLetterOrDigit (nRune)) || Rune.IsPunctuation (nRune) || Rune.IsSymbol (nRune)
|
|
||||||
? nCol
|
|
||||||
: lastValidCol;
|
|
||||||
|
|
||||||
if (fromRow != nRow)
|
if (fromRow != nRow)
|
||||||
{
|
{
|
||||||
nCol = 0;
|
nCol = 0;
|
||||||
@@ -477,6 +460,64 @@ internal class TextModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public (int startCol, int col, int row)? ProcessDoubleClickSelection (int fromStartCol, int fromCol, int fromRow, bool useSameRuneType, bool selectWordOnly)
|
||||||
|
{
|
||||||
|
List<Cell> line = GetLine (fromRow);
|
||||||
|
|
||||||
|
int startCol = fromStartCol;
|
||||||
|
int col = fromCol;
|
||||||
|
int row = fromRow;
|
||||||
|
|
||||||
|
(int col, int row)? newPos = WordForward (col, row, useSameRuneType);
|
||||||
|
|
||||||
|
if (newPos.HasValue)
|
||||||
|
{
|
||||||
|
col = row == newPos.Value.row ? newPos.Value.col : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startCol > 0
|
||||||
|
&& StringExtensions.ToString (line.GetRange (startCol, col - startCol).Select (c => c.Rune).ToList ()).Trim () == ""
|
||||||
|
&& (col - startCol > 1 || (col - startCol > 0 && line [startCol - 1].Rune == (Rune)' ')))
|
||||||
|
{
|
||||||
|
while (startCol > 0 && line [startCol - 1].Rune == (Rune)' ')
|
||||||
|
{
|
||||||
|
startCol--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newPos = WordBackward (col, row, useSameRuneType);
|
||||||
|
|
||||||
|
if (newPos is { })
|
||||||
|
{
|
||||||
|
startCol = row == newPos.Value.row ? newPos.Value.col : line.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectWordOnly)
|
||||||
|
{
|
||||||
|
List<Rune> selRunes = line.GetRange (startCol, col - startCol).Select (c => c.Rune).ToList ();
|
||||||
|
|
||||||
|
if (StringExtensions.ToString (selRunes).Trim () != "")
|
||||||
|
{
|
||||||
|
for (int i = selRunes.Count - 1; i > -1; i--)
|
||||||
|
{
|
||||||
|
if (selRunes [i] == (Rune)' ')
|
||||||
|
{
|
||||||
|
col--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromStartCol != startCol || fromCol != col || fromRow != row)
|
||||||
|
{
|
||||||
|
return (startCol, col, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
internal static int CalculateLeftColumn (List<Cell> t, int start, int end, int width, int tabWidth = 0)
|
internal static int CalculateLeftColumn (List<Cell> t, int start, int end, int width, int tabWidth = 0)
|
||||||
{
|
{
|
||||||
List<Rune> runes = new ();
|
List<Rune> runes = new ();
|
||||||
@@ -966,11 +1007,27 @@ internal class TextModel
|
|||||||
return RuneType.IsUnknown;
|
return RuneType.IsUnknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsSameRuneType (Rune newRune, RuneType runeType)
|
private bool IsSameRuneType (Rune newRune, RuneType runeType, bool useSameRuneType)
|
||||||
{
|
{
|
||||||
RuneType rt = GetRuneType (newRune);
|
RuneType rt = GetRuneType (newRune);
|
||||||
|
|
||||||
return rt == runeType;
|
if (useSameRuneType)
|
||||||
|
{
|
||||||
|
return rt == runeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (runeType)
|
||||||
|
{
|
||||||
|
case RuneType.IsSymbol:
|
||||||
|
case RuneType.IsPunctuation:
|
||||||
|
return rt is RuneType.IsSymbol or RuneType.IsPunctuation;
|
||||||
|
case RuneType.IsWhiteSpace:
|
||||||
|
case RuneType.IsLetterOrDigit:
|
||||||
|
case RuneType.IsUnknown:
|
||||||
|
return rt == runeType;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException (nameof (runeType), runeType, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool MatchWholeWord (string source, string matchText, int index = 0)
|
private bool MatchWholeWord (string source, string matchText, int index = 0)
|
||||||
@@ -992,7 +1049,7 @@ internal class TextModel
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool MoveNext (ref int col, ref int row, out Rune rune)
|
private bool MoveNext (ref int col, ref int row, out Rune rune, bool useSameRuneType)
|
||||||
{
|
{
|
||||||
List<Cell> line = GetLine (row);
|
List<Cell> line = GetLine (row);
|
||||||
|
|
||||||
@@ -1001,39 +1058,40 @@ internal class TextModel
|
|||||||
col++;
|
col++;
|
||||||
rune = line [col].Rune;
|
rune = line [col].Rune;
|
||||||
|
|
||||||
if (col + 1 == line.Count && !Rune.IsLetterOrDigit (rune) && !Rune.IsWhiteSpace (line [col - 1].Rune))
|
if (col + 1 == line.Count
|
||||||
|
&& !Rune.IsLetterOrDigit (rune)
|
||||||
|
&& !Rune.IsWhiteSpace (line [col - 1].Rune)
|
||||||
|
&& IsSameRuneType (line [col - 1].Rune, GetRuneType (rune), useSameRuneType))
|
||||||
{
|
{
|
||||||
col++;
|
col++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!Rune.IsWhiteSpace (rune)
|
||||||
|
&& (Rune.IsWhiteSpace (line [col - 1].Rune) || !IsSameRuneType (line [col - 1].Rune, GetRuneType (rune), useSameRuneType)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (col + 1 == line.Count)
|
if (col + 1 == line.Count)
|
||||||
{
|
{
|
||||||
col++;
|
col++;
|
||||||
|
rune = default (Rune);
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (row + 1 < Count)
|
// End of line
|
||||||
{
|
col = 0;
|
||||||
col = 0;
|
row++;
|
||||||
row++;
|
|
||||||
line = GetLine (row);
|
|
||||||
|
|
||||||
if (line.Count > 0)
|
|
||||||
{
|
|
||||||
rune = line [0].Rune;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rune = default (Rune);
|
rune = default (Rune);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool MovePrev (ref int col, ref int row, out Rune rune)
|
private bool MovePrev (ref int col, ref int row, out Rune rune, bool useSameRuneType)
|
||||||
{
|
{
|
||||||
List<Cell> line = GetLine (row);
|
List<Cell> line = GetLine (row);
|
||||||
|
|
||||||
@@ -1042,28 +1100,15 @@ internal class TextModel
|
|||||||
col--;
|
col--;
|
||||||
rune = line [col].Rune;
|
rune = line [col].Rune;
|
||||||
|
|
||||||
return true;
|
if ((!Rune.IsWhiteSpace (rune)
|
||||||
}
|
&& !Rune.IsWhiteSpace (line [col + 1].Rune)
|
||||||
|
&& !IsSameRuneType (line [col + 1].Rune, GetRuneType (rune), useSameRuneType))
|
||||||
if (row == 0)
|
|| (Rune.IsWhiteSpace (rune) && !Rune.IsWhiteSpace (line [col + 1].Rune)))
|
||||||
{
|
|
||||||
rune = default (Rune);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (row > 0)
|
|
||||||
{
|
|
||||||
row--;
|
|
||||||
line = GetLine (row);
|
|
||||||
col = line.Count - 1;
|
|
||||||
|
|
||||||
if (col >= 0)
|
|
||||||
{
|
{
|
||||||
rune = line [col].Rune;
|
return false;
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
rune = default (Rune);
|
rune = default (Rune);
|
||||||
|
|||||||
@@ -1015,6 +1015,19 @@ public class TextView : View, IDesignable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether the word forward and word backward navigation should use the same or equivalent rune type.
|
||||||
|
/// Default is <c>false</c> meaning using equivalent rune type.
|
||||||
|
/// </summary>
|
||||||
|
public bool UseSameRuneTypeForWords { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets whether the word navigation should select only the word itself without spaces around it or with the
|
||||||
|
/// spaces at right.
|
||||||
|
/// Default is <c>false</c> meaning that the spaces at right are included in the selection.
|
||||||
|
/// </summary>
|
||||||
|
public bool SelectWordOnlyOnDoubleClick { get; set; }
|
||||||
|
|
||||||
/// <summary>Allows clearing the <see cref="HistoryTextItemEventArgs"/> items updating the original text.</summary>
|
/// <summary>Allows clearing the <see cref="HistoryTextItemEventArgs"/> items updating the original text.</summary>
|
||||||
public void ClearHistoryChanges () { _historyText?.Clear (_model.GetAllLines ()); }
|
public void ClearHistoryChanges () { _historyText?.Clear (_model.GetAllLines ()); }
|
||||||
|
|
||||||
@@ -1689,29 +1702,19 @@ public class TextView : View, IDesignable
|
|||||||
}
|
}
|
||||||
|
|
||||||
ProcessMouseClick (ev, out List<Cell> line);
|
ProcessMouseClick (ev, out List<Cell> line);
|
||||||
(int col, int row)? newPos;
|
|
||||||
|
|
||||||
if (CurrentColumn == line.Count
|
|
||||||
|| (CurrentColumn > 0 && (line [CurrentColumn - 1].Rune.Value != ' ' || line [CurrentColumn].Rune.Value == ' ')))
|
|
||||||
{
|
|
||||||
newPos = _model.WordBackward (CurrentColumn, CurrentRow);
|
|
||||||
|
|
||||||
if (newPos.HasValue)
|
|
||||||
{
|
|
||||||
CurrentColumn = CurrentRow == newPos.Value.row ? newPos.Value.col : 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!IsSelecting)
|
if (!IsSelecting)
|
||||||
{
|
{
|
||||||
StartSelecting ();
|
StartSelecting ();
|
||||||
}
|
}
|
||||||
|
|
||||||
newPos = _model.WordForward (CurrentColumn, CurrentRow);
|
(int startCol, int col, int row)? newPos = _model.ProcessDoubleClickSelection (SelectionStartColumn, CurrentColumn, CurrentRow, UseSameRuneTypeForWords, SelectWordOnlyOnDoubleClick);
|
||||||
|
|
||||||
if (newPos is { } && newPos.HasValue)
|
if (newPos.HasValue)
|
||||||
{
|
{
|
||||||
CurrentColumn = CurrentRow == newPos.Value.row ? newPos.Value.col : line.Count;
|
SelectionStartColumn = newPos.Value.startCol;
|
||||||
|
CurrentColumn = newPos.Value.col;
|
||||||
|
CurrentRow = newPos.Value.row;
|
||||||
}
|
}
|
||||||
|
|
||||||
PositionCursor ();
|
PositionCursor ();
|
||||||
@@ -3380,7 +3383,7 @@ public class TextView : View, IDesignable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow);
|
(int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
|
||||||
|
|
||||||
if (newPos.HasValue && CurrentRow == newPos.Value.row)
|
if (newPos.HasValue && CurrentRow == newPos.Value.row)
|
||||||
{
|
{
|
||||||
@@ -3464,7 +3467,7 @@ public class TextView : View, IDesignable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow);
|
(int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
|
||||||
var restCount = 0;
|
var restCount = 0;
|
||||||
|
|
||||||
if (newPos.HasValue && CurrentRow == newPos.Value.row)
|
if (newPos.HasValue && CurrentRow == newPos.Value.row)
|
||||||
@@ -3754,7 +3757,7 @@ public class TextView : View, IDesignable
|
|||||||
|
|
||||||
private void MoveWordBackward ()
|
private void MoveWordBackward ()
|
||||||
{
|
{
|
||||||
(int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow);
|
(int col, int row)? newPos = _model.WordBackward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
|
||||||
|
|
||||||
if (newPos.HasValue)
|
if (newPos.HasValue)
|
||||||
{
|
{
|
||||||
@@ -3767,7 +3770,7 @@ public class TextView : View, IDesignable
|
|||||||
|
|
||||||
private void MoveWordForward ()
|
private void MoveWordForward ()
|
||||||
{
|
{
|
||||||
(int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow);
|
(int col, int row)? newPos = _model.WordForward (CurrentColumn, CurrentRow, UseSameRuneTypeForWords);
|
||||||
|
|
||||||
if (newPos.HasValue)
|
if (newPos.HasValue)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -607,6 +607,8 @@ public class TextFieldTests (ITestOutputHelper output)
|
|||||||
Assert.True (tf.NewKeyDownEvent (Key.CursorRight.WithShift.WithCtrl));
|
Assert.True (tf.NewKeyDownEvent (Key.CursorRight.WithShift.WithCtrl));
|
||||||
#endif
|
#endif
|
||||||
Assert.Equal ("is is a test.", tf.Text);
|
Assert.Equal ("is is a test.", tf.Text);
|
||||||
|
Assert.Equal ("is a test", tf.SelectedText);
|
||||||
|
Assert.True (tf.NewKeyDownEvent (Key.CursorRight.WithShift.WithCtrl));
|
||||||
Assert.Equal ("is a test.", tf.SelectedText);
|
Assert.Equal ("is a test.", tf.SelectedText);
|
||||||
Assert.Equal (13, tf.CursorPosition);
|
Assert.Equal (13, tf.CursorPosition);
|
||||||
Assert.True (tf.NewKeyDownEvent (Key.CursorLeft));
|
Assert.True (tf.NewKeyDownEvent (Key.CursorLeft));
|
||||||
@@ -1340,6 +1342,13 @@ public class TextFieldTests (ITestOutputHelper output)
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
|
Assert.Equal (31, _textField.CursorPosition);
|
||||||
|
Assert.Equal (-1, _textField.SelectedStart);
|
||||||
|
Assert.Equal (0, _textField.SelectedLength);
|
||||||
|
Assert.Null (_textField.SelectedText);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
Assert.Equal (32, _textField.CursorPosition);
|
Assert.Equal (32, _textField.CursorPosition);
|
||||||
Assert.Equal (-1, _textField.SelectedStart);
|
Assert.Equal (-1, _textField.SelectedStart);
|
||||||
Assert.Equal (0, _textField.SelectedLength);
|
Assert.Equal (0, _textField.SelectedLength);
|
||||||
@@ -1501,6 +1510,13 @@ public class TextFieldTests (ITestOutputHelper output)
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
|
Assert.Equal (31, _textField.CursorPosition);
|
||||||
|
Assert.Equal (0, _textField.SelectedStart);
|
||||||
|
Assert.Equal (31, _textField.SelectedLength);
|
||||||
|
Assert.Equal ("TAB to jump between text fields", _textField.SelectedText);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
Assert.Equal (32, _textField.CursorPosition);
|
Assert.Equal (32, _textField.CursorPosition);
|
||||||
Assert.Equal (0, _textField.SelectedStart);
|
Assert.Equal (0, _textField.SelectedStart);
|
||||||
Assert.Equal (32, _textField.SelectedLength);
|
Assert.Equal (32, _textField.SelectedLength);
|
||||||
@@ -1550,6 +1566,13 @@ public class TextFieldTests (ITestOutputHelper output)
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
|
Assert.Equal (31, _textField.CursorPosition);
|
||||||
|
Assert.Equal (10, _textField.SelectedStart);
|
||||||
|
Assert.Equal (21, _textField.SelectedLength);
|
||||||
|
Assert.Equal ("p between text fields", _textField.SelectedText);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
Assert.Equal (32, _textField.CursorPosition);
|
Assert.Equal (32, _textField.CursorPosition);
|
||||||
Assert.Equal (10, _textField.SelectedStart);
|
Assert.Equal (10, _textField.SelectedStart);
|
||||||
Assert.Equal (22, _textField.SelectedLength);
|
Assert.Equal (22, _textField.SelectedLength);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -348,6 +348,9 @@ public class TextFieldTests
|
|||||||
tf.BeginInit ();
|
tf.BeginInit ();
|
||||||
tf.EndInit ();
|
tf.EndInit ();
|
||||||
|
|
||||||
|
Assert.False (tf.UseSameRuneTypeForWords);
|
||||||
|
Assert.Equal (22, tf.CursorPosition);
|
||||||
|
|
||||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||||
Assert.Equal (15, tf.CursorPosition);
|
Assert.Equal (15, tf.CursorPosition);
|
||||||
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
tf.NewKeyDownEvent (Key.CursorLeft.WithCtrl);
|
||||||
@@ -402,14 +405,14 @@ public class TextFieldTests
|
|||||||
new () { Position = new (idx, 1), Flags = MouseFlags.Button1DoubleClicked, View = tf }
|
new () { Position = new (idx, 1), Flags = MouseFlags.Button1DoubleClicked, View = tf }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
Assert.Equal ("movie.", tf.SelectedText);
|
Assert.Equal ("movie", tf.SelectedText);
|
||||||
|
|
||||||
Assert.True (
|
Assert.True (
|
||||||
tf.NewMouseEvent (
|
tf.NewMouseEvent (
|
||||||
new () { Position = new (idx + 1, 1), Flags = MouseFlags.Button1DoubleClicked, View = tf }
|
new () { Position = new (idx + 1, 1), Flags = MouseFlags.Button1DoubleClicked, View = tf }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
Assert.Equal ("movie.", tf.SelectedText);
|
Assert.Equal ("movie", tf.SelectedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
2408
Tests/UnitTestsParallelizable/Views/TextViewTests.cs
Normal file
2408
Tests/UnitTestsParallelizable/Views/TextViewTests.cs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user