diff --git a/Directory.Packages.props b/Directory.Packages.props
index 511ec4d415..779a90a181 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -47,6 +47,7 @@
+
diff --git a/Examples/UICatalog/Scenarios/Notepad.cs b/Examples/UICatalog/Scenarios/Notepad.cs
index 988e4764d5..94eaa57c64 100644
--- a/Examples/UICatalog/Scenarios/Notepad.cs
+++ b/Examples/UICatalog/Scenarios/Notepad.cs
@@ -7,7 +7,7 @@ namespace UICatalog.Scenarios;
[ScenarioMetadata ("Notepad", "Multi-tab text editor using the Tabs control.")]
[ScenarioCategory ("Controls")]
[ScenarioCategory ("Tabs")]
-[ScenarioCategory ("TextView")]
+[ScenarioCategory ("TextViewEditor")]
public class Notepad : Scenario
{
private IApplication? _app;
@@ -271,8 +271,8 @@ private void Open (FileInfo? fileInfo, string tabName)
}
OpenedFile tab = new (this) { Title = tabName, File = fileInfo };
- tab.CreateAndAddTextView (fileInfo);
- tab.RegisterTextViewEvents ();
+ tab.CreateAndAddEditor (fileInfo);
+ tab.RegisterEditorEvents ();
_focusedTabs.Add (tab);
_focusedTabs.Value = tab;
@@ -344,7 +344,7 @@ private int GetSelectedTextLength ()
{
if (_focusedTabs?.Value is OpenedFile tab)
{
- return tab.TextView?.Text.Length ?? 0;
+ return tab.Editor?.Text.Length ?? 0;
}
return 0;
@@ -360,7 +360,7 @@ private void Tabs_ValueChanged (object? sender, ValueChangedEventArgs e)
if (e.NewValue is OpenedFile tab)
{
- len = tab.TextView?.Text.Length ?? 0;
+ len = tab.Editor?.Text.Length ?? 0;
}
LenShortcut.Title = $"Len:{len}";
@@ -376,16 +376,16 @@ private class OpenedFile (Notepad notepad) : View
public FileInfo? File { get; set; }
/// Gets whether this tab is a pristine new document — never opened to a file and has no content.
- public bool IsPristine => File is null && string.IsNullOrEmpty (TextView?.Text);
+ public bool IsPristine => File is null && string.IsNullOrEmpty (Editor?.Text);
- public TextView? TextView { get; private set; }
+ public TextViewEditor? Editor { get; private set; }
/// The text of the tab the last time it was saved.
private string? _savedText;
- public bool UnsavedChanges => TextView is { } && !string.Equals (_savedText, TextView.Text);
+ public bool UnsavedChanges => Editor is { } && !string.Equals (_savedText, Editor.Text);
- public void CreateAndAddTextView (FileInfo? file)
+ public void CreateAndAddEditor (FileInfo? file)
{
var initialText = string.Empty;
@@ -394,25 +394,24 @@ public void CreateAndAddTextView (FileInfo? file)
initialText = System.IO.File.ReadAllText (file.FullName);
}
- TextView = new TextView
+ Editor = new TextViewEditor
{
X = 0,
Y = 0,
Width = Dim.Fill (),
Height = Dim.Fill (),
- Text = initialText,
- TabKeyAddsTab = false
+ Text = initialText
};
_savedText = initialText;
- Add (TextView);
+ Add (Editor);
}
/// Loads a file into an existing tab, replacing its content.
public void LoadFile (FileInfo file)
{
- if (TextView is null)
+ if (Editor is null)
{
return;
}
@@ -426,49 +425,49 @@ public void LoadFile (FileInfo file)
// Set _savedText first so the ContentsChanged handler sees matching text (not dirty).
_savedText = text;
- TextView.Text = text;
+ Editor.Text = text;
}
- public void RegisterTextViewEvents ()
+ public void RegisterEditorEvents ()
{
- if (TextView is null)
+ if (Editor is null)
{
return;
}
// when user makes changes rename tab to indicate unsaved
- TextView.ContentsChanged += (_, _) =>
- {
- // if current text doesn't match saved text
- bool areDiff = UnsavedChanges;
-
- if (areDiff)
- {
- if (!Title.EndsWith ('*'))
- {
- Title = Title + "*";
- }
- }
- else
- {
- if (Title.EndsWith ('*'))
- {
- Title = Title.TrimEnd ('*');
- }
- }
-
- notepad.LenShortcut?.Title = $"Len:{TextView.Text.Length}";
- };
+ Editor.ContentsChanged += (_, _) =>
+ {
+ // if current text doesn't match saved text
+ bool areDiff = UnsavedChanges;
+
+ if (areDiff)
+ {
+ if (!Title.EndsWith ('*'))
+ {
+ Title = Title + "*";
+ }
+ }
+ else
+ {
+ if (Title.EndsWith ('*'))
+ {
+ Title = Title.TrimEnd ('*');
+ }
+ }
+
+ notepad.LenShortcut?.Title = $"Len:{Editor.Text.Length}";
+ };
}
internal void Save ()
{
- if (TextView is null || File is null || string.IsNullOrWhiteSpace (File.FullName))
+ if (Editor is null || File is null || string.IsNullOrWhiteSpace (File.FullName))
{
return;
}
- string newText = TextView.Text;
+ string newText = Editor.Text;
System.IO.File.WriteAllText (File.FullName, newText);
_savedText = newText;
diff --git a/Examples/UICatalog/UICatalog.csproj b/Examples/UICatalog/UICatalog.csproj
index 1dc8d9f303..1de677e941 100644
--- a/Examples/UICatalog/UICatalog.csproj
+++ b/Examples/UICatalog/UICatalog.csproj
@@ -35,6 +35,7 @@
+
diff --git a/Terminal.Gui.TextViewEditor/Terminal.Gui.TextViewEditor.csproj b/Terminal.Gui.TextViewEditor/Terminal.Gui.TextViewEditor.csproj
new file mode 100644
index 0000000000..05e0715849
--- /dev/null
+++ b/Terminal.Gui.TextViewEditor/Terminal.Gui.TextViewEditor.csproj
@@ -0,0 +1,18 @@
+
+
+ Terminal.Gui.TextViewEditor
+ net10.0
+ 14
+ enable
+ enable
+ Terminal.Gui.Views
+ Terminal.Gui.TextViewEditor
+ Wraps the Terminal.Gui.Editor view with a TextView-compatible API to ease porting.
+ true
+
+
+
+
+
+
+
diff --git a/Terminal.Gui.TextViewEditor/TextViewEditor.cs b/Terminal.Gui.TextViewEditor/TextViewEditor.cs
new file mode 100644
index 0000000000..b8e3337bcd
--- /dev/null
+++ b/Terminal.Gui.TextViewEditor/TextViewEditor.cs
@@ -0,0 +1,264 @@
+using System.Drawing;
+using Terminal.Gui.Document;
+using Terminal.Gui.ViewBase;
+
+namespace Terminal.Gui.Views;
+
+///
+/// A that wraps the Terminal.Gui.Editor package's
+/// view with an API that is compatible with
+/// , easing migration from to the
+/// gui-cs/Editor package.
+///
+///
+///
+/// delegates all text editing to the underlying
+/// while exposing properties and methods that match
+/// the API. This enables existing code that uses
+/// to migrate incrementally by swapping the type to with
+/// minimal source changes.
+///
+///
+/// Not all features are supported. Unsupported features will
+/// throw or no-op as documented on each member.
+///
+///
+public class TextViewEditor : View
+{
+ // Initialized to null! because the base View constructor calls the virtual Text setter
+ // (via SetupText) before this constructor body runs. The null guard in Text protects against this.
+ private readonly Terminal.Gui.Editor.Editor _editor = null!;
+
+ ///
+ /// Initializes a new instance of .
+ ///
+ public TextViewEditor ()
+ {
+ CanFocus = true;
+
+ _editor = new Terminal.Gui.Editor.Editor
+ {
+ X = 0,
+ Y = 0,
+ Width = Dim.Fill (),
+ Height = Dim.Fill ()
+ };
+
+ _editor.CaretChanged += (_, _) => CaretChanged?.Invoke (this, EventArgs.Empty);
+ _editor.SelectionChanged += (_, _) => SelectionChanged?.Invoke (this, EventArgs.Empty);
+
+ // Subscribe to document text changes
+ _editor.Document.Changed += (_, _) => OnContentsChanged ();
+
+ Add (_editor);
+ }
+
+ ///
+ /// Gets or sets the text content. This is the primary -compatible
+ /// text property.
+ ///
+ ///
+ /// Setting this property replaces the entire document content and resets the cursor
+ /// position to the beginning.
+ ///
+ public override string Text
+ {
+ get => _editor?.Document.Text ?? string.Empty;
+ set
+ {
+ if (_editor is null)
+ {
+ return;
+ }
+
+ _editor.Document.Text = value ?? string.Empty;
+ _editor.CaretOffset = 0;
+ OnTextChanged ();
+ }
+ }
+
+ ///
+ /// Gets the cursor column (zero-based offset within the current line).
+ ///
+ public int CurrentColumn
+ {
+ get
+ {
+ DocumentLine line = _editor.Document.GetLineByOffset (_editor.CaretOffset);
+
+ return _editor.CaretOffset - line.Offset;
+ }
+ }
+
+ ///
+ /// Gets the current cursor row (zero-based line number).
+ ///
+ public int CurrentRow
+ {
+ get
+ {
+ DocumentLine line = _editor.Document.GetLineByOffset (_editor.CaretOffset);
+
+ return line.LineNumber - 1; // DocumentLine uses 1-based line numbers
+ }
+ }
+
+ ///
+ /// Gets or sets the cursor position as a where
+ /// X is the column and Y is the row.
+ ///
+ public Point InsertionPoint
+ {
+ get => new (CurrentColumn, CurrentRow);
+ set
+ {
+ int row = Math.Max (0, Math.Min (value.Y, _editor.Document.LineCount - 1));
+ DocumentLine line = _editor.Document.GetLineByNumber (row + 1);
+ int col = Math.Max (0, Math.Min (value.X, line.Length));
+ _editor.CaretOffset = line.Offset + col;
+ }
+ }
+
+ ///
+ /// Gets the number of lines in the document.
+ ///
+ public int Lines => _editor.Document.LineCount;
+
+ ///
+ /// Gets or sets whether the editor is in read-only mode.
+ ///
+ public bool ReadOnly
+ {
+ get => _editor.ReadOnly;
+ set => _editor.ReadOnly = value;
+ }
+
+ ///
+ /// Gets or sets the tab width (indentation size). Equivalent to .
+ ///
+ public int TabWidth
+ {
+ get => _editor.IndentationSize;
+ set => _editor.IndentationSize = Math.Max (value, 1);
+ }
+
+ ///
+ /// Gets the selected text, or if no selection exists.
+ ///
+ public string SelectedText
+ {
+ get
+ {
+ if (!_editor.HasSelection)
+ {
+ return string.Empty;
+ }
+
+ return _editor.SelectedText ?? string.Empty;
+ }
+ }
+
+ ///
+ /// Gets whether the user currently has text selected.
+ ///
+ public bool IsSelecting => _editor.HasSelection;
+
+ ///
+ /// Gets the length of the current selection.
+ ///
+ public int SelectedLength => _editor.SelectionLength;
+
+ ///
+ /// Gets whether the text has been modified since last load or clear.
+ ///
+ public bool IsDirty => _editor.Document.UndoStack.CanUndo;
+
+ ///
+ /// Raised when the caret (cursor) position changes.
+ ///
+ public event EventHandler? CaretChanged;
+
+ ///
+ /// Raised when the selection changes.
+ ///
+ public event EventHandler? SelectionChanged;
+
+ ///
+ /// Raised when the contents of the editor are changed.
+ ///
+ public event EventHandler? ContentsChanged;
+
+ ///
+ /// Loads text from a file path.
+ ///
+ /// The file path to load.
+ /// if the file was loaded successfully.
+ public bool Load (string path)
+ {
+ if (!File.Exists (path))
+ {
+ return false;
+ }
+
+ string content = File.ReadAllText (path);
+ _editor.Document.Text = content;
+ _editor.CaretOffset = 0;
+ _editor.Document.UndoStack.MarkAsOriginalFile ();
+ OnTextChanged ();
+
+ return true;
+ }
+
+ ///
+ /// Loads text from a stream.
+ ///
+ /// The stream to load from.
+ public void Load (Stream stream)
+ {
+ using StreamReader reader = new (stream);
+ string content = reader.ReadToEnd ();
+ _editor.Document.Text = content;
+ _editor.CaretOffset = 0;
+ _editor.Document.UndoStack.MarkAsOriginalFile ();
+ OnTextChanged ();
+ }
+
+ ///
+ /// Selects all text in the editor.
+ ///
+ public void SelectAll ()
+ {
+ _editor.SelectAll ();
+ }
+
+ ///
+ /// Clears the current selection.
+ ///
+ public void ClearSelection ()
+ {
+ _editor.ClearSelection ();
+ }
+
+ ///
+ /// Gets the underlying instance for advanced usage.
+ ///
+ ///
+ /// Use this property to access Editor-specific features not exposed by the
+ /// -compatible API, such as folding, syntax highlighting,
+ /// multi-caret editing, and the rendering pipeline.
+ ///
+ public Terminal.Gui.Editor.Editor UnderlyingEditor => _editor;
+
+ ///
+ /// Gets the underlying for direct document manipulation.
+ ///
+ public TextDocument Document => _editor.Document;
+
+ ///
+ /// Raises the event.
+ ///
+ protected virtual void OnContentsChanged ()
+ {
+ ContentsChanged?.Invoke (this, EventArgs.Empty);
+ }
+}
diff --git a/Terminal.sln b/Terminal.sln
index de4c68a853..95765706a6 100644
--- a/Terminal.sln
+++ b/Terminal.sln
@@ -158,6 +158,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui.Analyzers.Inte
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PerformanceTests", "Tests\PerformanceTests\PerformanceTests.csproj", "{6E98BACA-E6B6-47A0-B45C-B624F8E74EC2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui.TextViewEditor", "Terminal.Gui.TextViewEditor\Terminal.Gui.TextViewEditor.csproj", "{048A9D63-B901-404A-8BF0-57DC66E9100F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TextViewEditorTests", "Tests\TextViewEditorTests\TextViewEditorTests.csproj", "{9C89E569-2AB8-4970-9D68-793709940604}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -468,6 +472,30 @@ Global
{6E98BACA-E6B6-47A0-B45C-B624F8E74EC2}.Release|x64.Build.0 = Release|Any CPU
{6E98BACA-E6B6-47A0-B45C-B624F8E74EC2}.Release|x86.ActiveCfg = Release|Any CPU
{6E98BACA-E6B6-47A0-B45C-B624F8E74EC2}.Release|x86.Build.0 = Release|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Debug|x64.Build.0 = Debug|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Debug|x86.Build.0 = Debug|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Release|x64.ActiveCfg = Release|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Release|x64.Build.0 = Release|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Release|x86.ActiveCfg = Release|Any CPU
+ {048A9D63-B901-404A-8BF0-57DC66E9100F}.Release|x86.Build.0 = Release|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Debug|x64.Build.0 = Debug|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Debug|x86.Build.0 = Debug|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Release|x64.ActiveCfg = Release|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Release|x64.Build.0 = Release|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Release|x86.ActiveCfg = Release|Any CPU
+ {9C89E569-2AB8-4970-9D68-793709940604}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -499,6 +527,7 @@ Global
{70802F77-F259-44C6-9522-46FCE2FD754E} = {3DD033C0-E023-47BF-A808-9CCE30873C3E}
{3116547F-A8F2-4189-BC22-0B47C757164C} = {3DD033C0-E023-47BF-A808-9CCE30873C3E}
{6E98BACA-E6B6-47A0-B45C-B624F8E74EC2} = {A589126F-C71A-4FEE-B7EA-2DCA1ADF6A46}
+ {9C89E569-2AB8-4970-9D68-793709940604} = {A589126F-C71A-4FEE-B7EA-2DCA1ADF6A46}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9F8F8A4D-7B8D-4C2A-AC5E-CD7117F74C03}
diff --git a/Tests/TextViewEditorTests/TextViewEditorBasicTests.cs b/Tests/TextViewEditorTests/TextViewEditorBasicTests.cs
new file mode 100644
index 0000000000..cbeae9dd06
--- /dev/null
+++ b/Tests/TextViewEditorTests/TextViewEditorBasicTests.cs
@@ -0,0 +1,237 @@
+// Copilot
+
+using Terminal.Gui.Views;
+
+namespace TextViewEditorTests;
+
+///
+/// Unit tests for .
+///
+public class TextViewEditorBasicTests
+{
+ [Fact]
+ public void Constructor_Creates_Instance ()
+ {
+ TextViewEditor editor = new ();
+
+ Assert.NotNull (editor);
+ Assert.NotNull (editor.UnderlyingEditor);
+ Assert.NotNull (editor.Document);
+ }
+
+ [Fact]
+ public void Text_SetAndGet_RoundTrips ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = "Hello, World!";
+
+ Assert.Equal ("Hello, World!", editor.Text);
+ }
+
+ [Fact]
+ public void Text_SetNull_BecomesEmpty ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = null!;
+
+ Assert.Equal (string.Empty, editor.Text);
+ }
+
+ [Fact]
+ public void Lines_ReturnsCorrectCount ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = "Line1\nLine2\nLine3";
+
+ Assert.Equal (3, editor.Lines);
+ }
+
+ [Fact]
+ public void CurrentRow_AfterSetText_IsZero ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = "Line1\nLine2";
+
+ Assert.Equal (0, editor.CurrentRow);
+ }
+
+ [Fact]
+ public void CurrentColumn_AfterSetText_IsZero ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = "Hello";
+
+ Assert.Equal (0, editor.CurrentColumn);
+ }
+
+ [Fact]
+ public void InsertionPoint_SetAndGet ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = "Line1\nLine2\nLine3";
+ editor.InsertionPoint = new Point (3, 1);
+
+ Assert.Equal (1, editor.CurrentRow);
+ Assert.Equal (3, editor.CurrentColumn);
+ Assert.Equal (new Point (3, 1), editor.InsertionPoint);
+ }
+
+ [Fact]
+ public void InsertionPoint_ClampsToBounds ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = "AB\nCD";
+
+ // Set beyond line length
+ editor.InsertionPoint = new Point (100, 0);
+ Assert.Equal (2, editor.CurrentColumn);
+
+ // Set beyond line count
+ editor.InsertionPoint = new Point (0, 100);
+ Assert.Equal (1, editor.CurrentRow);
+ }
+
+ [Fact]
+ public void ReadOnly_DefaultFalse ()
+ {
+ TextViewEditor editor = new ();
+
+ Assert.False (editor.ReadOnly);
+ }
+
+ [Fact]
+ public void ReadOnly_SetTrue ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.ReadOnly = true;
+
+ Assert.True (editor.ReadOnly);
+ }
+
+ [Fact]
+ public void TabWidth_DefaultIs4 ()
+ {
+ TextViewEditor editor = new ();
+
+ Assert.Equal (4, editor.TabWidth);
+ }
+
+ [Fact]
+ public void TabWidth_SetAndGet ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.TabWidth = 8;
+
+ Assert.Equal (8, editor.TabWidth);
+ }
+
+ [Fact]
+ public void TabWidth_MinimumIsOne ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.TabWidth = 0;
+
+ Assert.Equal (1, editor.TabWidth);
+ }
+
+ [Fact]
+ public void IsSelecting_DefaultFalse ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = "Hello";
+
+ Assert.False (editor.IsSelecting);
+ }
+
+ [Fact]
+ public void SelectedText_WhenNoSelection_ReturnsEmpty ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = "Hello";
+
+ Assert.Equal (string.Empty, editor.SelectedText);
+ }
+
+ [Fact]
+ public void SelectedLength_WhenNoSelection_ReturnsZero ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = "Hello";
+
+ Assert.Equal (0, editor.SelectedLength);
+ }
+
+ [Fact]
+ public void Load_Stream_LoadsContent ()
+ {
+ TextViewEditor editor = new ();
+
+ using MemoryStream stream = new (System.Text.Encoding.UTF8.GetBytes ("Stream content"));
+ editor.Load (stream);
+
+ Assert.Equal ("Stream content", editor.Text);
+ }
+
+ [Fact]
+ public void Load_NonExistentFile_ReturnsFalse ()
+ {
+ TextViewEditor editor = new ();
+
+ bool result = editor.Load ("/nonexistent/path/file.txt");
+
+ Assert.False (result);
+ }
+
+ [Fact]
+ public void ContentsChanged_RaisedOnTextChange ()
+ {
+ TextViewEditor editor = new ();
+ bool raised = false;
+ editor.ContentsChanged += (_, _) => raised = true;
+
+ editor.Text = "new content";
+
+ // ContentsChanged is raised via the document's Changed event
+ // When Text is set, it replaces the document content which fires Changed
+ Assert.True (raised);
+ }
+
+ [Fact]
+ public void SelectAll_SelectsEntireDocument ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = "Hello";
+ editor.SelectAll ();
+
+ Assert.True (editor.IsSelecting);
+ Assert.Equal ("Hello", editor.SelectedText);
+ Assert.Equal (5, editor.SelectedLength);
+ }
+
+ [Fact]
+ public void ClearSelection_RemovesSelection ()
+ {
+ TextViewEditor editor = new ();
+
+ editor.Text = "Hello";
+ editor.SelectAll ();
+ editor.ClearSelection ();
+
+ Assert.False (editor.IsSelecting);
+ Assert.Equal (string.Empty, editor.SelectedText);
+ }
+}
diff --git a/Tests/TextViewEditorTests/TextViewEditorTests.csproj b/Tests/TextViewEditorTests/TextViewEditorTests.csproj
new file mode 100644
index 0000000000..9e9dcfaef4
--- /dev/null
+++ b/Tests/TextViewEditorTests/TextViewEditorTests.csproj
@@ -0,0 +1,27 @@
+
+
+ Exe
+ true
+ true
+ enable
+ false
+ enable
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/nuget.config b/nuget.config
index 4dcc41f23c..147177efe2 100644
--- a/nuget.config
+++ b/nuget.config
@@ -14,6 +14,7 @@
+