diff --git a/ReactiveExample/ReactiveExample.csproj b/ReactiveExample/ReactiveExample.csproj
index b57731a0f..30716dd37 100644
--- a/ReactiveExample/ReactiveExample.csproj
+++ b/ReactiveExample/ReactiveExample.csproj
@@ -4,8 +4,8 @@
net6.0
-
-
+
+
diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs
index 631fe10cd..fd086c3b2 100644
--- a/Terminal.Gui/Views/HexView.cs
+++ b/Terminal.Gui/Views/HexView.cs
@@ -37,27 +37,44 @@ namespace Terminal.Gui {
public class HexView : View {
SortedDictionary edits = new SortedDictionary ();
Stream source;
- long displayStart, position;
+ long displayStart, pos;
bool firstNibble, leftSide;
+ private long position {
+ get => pos;
+ set {
+ pos = value;
+ OnPositionChanged ();
+ }
+ }
+
///
- /// Initialzies a class using layout.
+ /// Initializes a class using layout.
///
/// The to view and edit as hex, this must support seeking, or an exception will be thrown.
public HexView (Stream source) : base ()
{
Source = source;
- this.source = source;
CanFocus = true;
leftSide = true;
firstNibble = true;
}
///
- /// Initialzies a class using layout.
+ /// Initializes a class using layout.
///
public HexView () : this (source: new MemoryStream ()) { }
+ ///
+ /// Event to be invoked when an edit is made on the .
+ ///
+ public event Action> Edited;
+
+ ///
+ /// Event to be invoked when the position and cursor position changes.
+ ///
+ public event Action PositionChanged;
+
///
/// Sets or gets the the is operating on; the stream must support seeking ( == true).
///
@@ -71,13 +88,17 @@ namespace Terminal.Gui {
throw new ArgumentException ("The source stream must be seekable (CanSeek property)", "source");
source = value;
+ if (displayStart > source.Length)
+ DisplayStart = 0;
+ if (position > source.Length)
+ position = 0;
SetNeedsDisplay ();
}
}
internal void SetDisplayStart (long value)
{
- if (value >= source.Length)
+ if (value > 0 && value >= source.Length)
displayStart = source.Length - 1;
else if (value < 0)
displayStart = 0;
@@ -101,7 +122,14 @@ namespace Terminal.Gui {
const int displayWidth = 9;
const int bsize = 4;
- int bytesPerLine;
+ int bpl;
+ private int bytesPerLine {
+ get => bpl;
+ set {
+ bpl = value;
+ OnPositionChanged ();
+ }
+ }
///
public override Rect Frame {
@@ -109,10 +137,10 @@ namespace Terminal.Gui {
set {
base.Frame = value;
- // Small buffers will just show the position, with 4 bytes
- bytesPerLine = 4;
+ // Small buffers will just show the position, with the bsize field value (4 bytes)
+ bytesPerLine = bsize;
if (value.Width - displayWidth > 17)
- bytesPerLine = 4 * ((value.Width - displayWidth) / 18);
+ bytesPerLine = bsize * ((value.Width - displayWidth) / 18);
}
}
@@ -144,8 +172,8 @@ namespace Terminal.Gui {
var frame = Frame;
- var nblocks = bytesPerLine / 4;
- var data = new byte [nblocks * 4 * frame.Height];
+ var nblocks = bytesPerLine / bsize;
+ var data = new byte [nblocks * bsize * frame.Height];
Source.Position = displayStart;
var n = source.Read (data, 0, data.Length);
@@ -159,38 +187,34 @@ namespace Terminal.Gui {
Move (0, line);
Driver.SetAttribute (ColorScheme.HotNormal);
- Driver.AddStr (string.Format ("{0:x8} ", displayStart + line * nblocks * 4));
+ Driver.AddStr (string.Format ("{0:x8} ", displayStart + line * nblocks * bsize));
currentAttribute = ColorScheme.HotNormal;
SetAttribute (GetNormalColor ());
for (int block = 0; block < nblocks; block++) {
- for (int b = 0; b < 4; b++) {
- var offset = (line * nblocks * 4) + block * 4 + b;
- bool edited;
- var value = GetData (data, offset, out edited);
+ for (int b = 0; b < bsize; b++) {
+ var offset = (line * nblocks * bsize) + block * bsize + b;
+ var value = GetData (data, offset, out bool edited);
if (offset + displayStart == position || edited)
SetAttribute (leftSide ? activeColor : trackingColor);
else
SetAttribute (GetNormalColor ());
- Driver.AddStr (offset >= n ? " " : string.Format ("{0:x2}", value));
+ Driver.AddStr (offset >= n && !edited ? " " : string.Format ("{0:x2}", value));
SetAttribute (GetNormalColor ());
Driver.AddRune (' ');
}
Driver.AddStr (block + 1 == nblocks ? " " : "| ");
}
-
- for (int bitem = 0; bitem < nblocks * 4; bitem++) {
- var offset = line * nblocks * 4 + bitem;
-
- bool edited = false;
- Rune c = ' ';
- if (offset >= n)
+ for (int bitem = 0; bitem < nblocks * bsize; bitem++) {
+ var offset = line * nblocks * bsize + bitem;
+ var b = GetData (data, offset, out bool edited);
+ Rune c;
+ if (offset >= n && !edited)
c = ' ';
else {
- var b = GetData (data, offset, out edited);
if (b < 32)
c = '.';
else if (b > 127)
@@ -214,7 +238,6 @@ namespace Terminal.Gui {
Driver.SetAttribute (attribute);
}
}
-
}
///
@@ -223,13 +246,13 @@ namespace Terminal.Gui {
var delta = (int)(position - displayStart);
var line = delta / bytesPerLine;
var item = delta % bytesPerLine;
- var block = item / 4;
- var column = (item % 4) * 3;
+ var block = item / bsize;
+ var column = (item % bsize) * 3;
if (leftSide)
Move (displayWidth + block * 14 + column + (firstNibble ? 0 : 1), line);
else
- Move (displayWidth + (bytesPerLine / 4) * 14 + item - 1, line);
+ Move (displayWidth + (bytesPerLine / bsize) * 14 + item - 1, line);
}
void RedisplayLine (long pos)
@@ -240,13 +263,80 @@ namespace Terminal.Gui {
SetNeedsDisplay (new Rect (0, line, Frame.Width, 1));
}
- void CursorRight ()
+ bool MoveEndOfLine ()
+ {
+ position = Math.Min ((position / bytesPerLine * bytesPerLine) + bytesPerLine - 1, source.Length);
+ SetNeedsDisplay ();
+
+ return true;
+ }
+
+ bool MoveStartOfLine ()
+ {
+ position = position / bytesPerLine * bytesPerLine;
+ SetNeedsDisplay ();
+
+ return true;
+ }
+
+ bool MoveEnd ()
+ {
+ position = source.Length;
+ if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
+ SetDisplayStart (position);
+ SetNeedsDisplay ();
+ } else
+ RedisplayLine (position);
+
+ return true;
+ }
+
+ bool MoveHome ()
+ {
+ DisplayStart = 0;
+ SetNeedsDisplay ();
+
+ return true;
+ }
+
+ bool ToggleSide ()
+ {
+ leftSide = !leftSide;
+ RedisplayLine (position);
+ firstNibble = true;
+
+ return true;
+ }
+
+ bool MoveLeft ()
+ {
+ RedisplayLine (position);
+ if (leftSide) {
+ if (!firstNibble) {
+ firstNibble = true;
+ return true;
+ }
+ firstNibble = false;
+ }
+ if (position == 0)
+ return true;
+ if (position - 1 < DisplayStart) {
+ SetDisplayStart (displayStart - bytesPerLine);
+ SetNeedsDisplay ();
+ } else
+ RedisplayLine (position);
+ position--;
+
+ return true;
+ }
+
+ bool MoveRight ()
{
RedisplayLine (position);
if (leftSide) {
if (firstNibble) {
firstNibble = false;
- return;
+ return true;
} else
firstNibble = true;
}
@@ -257,32 +347,44 @@ namespace Terminal.Gui {
SetNeedsDisplay ();
} else
RedisplayLine (position);
+
+ return true;
}
- void MoveUp (int bytes)
+ bool MoveUp (int bytes)
{
RedisplayLine (position);
- position -= bytes;
- if (position < 0)
- position = 0;
+ if (position - bytes > -1)
+ position -= bytes;
if (position < DisplayStart) {
SetDisplayStart (DisplayStart - bytes);
SetNeedsDisplay ();
} else
RedisplayLine (position);
+ return true;
}
- void MoveDown (int bytes)
+ bool MoveDown (int bytes)
{
RedisplayLine (position);
if (position + bytes < source.Length)
position += bytes;
+ else if ((bytes == bytesPerLine * Frame.Height && source.Length >= (DisplayStart + bytesPerLine * Frame.Height))
+ || (bytes <= (bytesPerLine * Frame.Height - bytesPerLine) && source.Length <= (DisplayStart + bytesPerLine * Frame.Height))) {
+ var p = position;
+ while (p + bytesPerLine < source.Length) {
+ p += bytesPerLine;
+ }
+ position = p;
+ }
if (position >= (DisplayStart + bytesPerLine * Frame.Height)) {
SetDisplayStart (DisplayStart + bytes);
SetNeedsDisplay ();
} else
RedisplayLine (position);
+
+ return true;
}
///
@@ -290,52 +392,43 @@ namespace Terminal.Gui {
{
switch (keyEvent.Key) {
case Key.CursorLeft:
- RedisplayLine (position);
- if (leftSide) {
- if (!firstNibble) {
- firstNibble = true;
- return true;
- }
- firstNibble = false;
- }
- if (position == 0)
- return true;
- if (position - 1 < DisplayStart) {
- SetDisplayStart (displayStart - bytesPerLine);
- SetNeedsDisplay ();
- } else
- RedisplayLine (position);
- position--;
- break;
+ return MoveLeft ();
case Key.CursorRight:
- CursorRight ();
- break;
+ return MoveRight ();
case Key.CursorDown:
- MoveDown (bytesPerLine);
- break;
+ return MoveDown (bytesPerLine);
case Key.CursorUp:
- MoveUp (bytesPerLine);
- break;
+ return MoveUp (bytesPerLine);
case Key.Enter:
- leftSide = !leftSide;
- RedisplayLine (position);
- firstNibble = true;
- break;
+ return ToggleSide ();
case ((int)'v' + Key.AltMask):
case Key.PageUp:
- MoveUp (bytesPerLine * Frame.Height);
- break;
+ return MoveUp (bytesPerLine * Frame.Height);
case Key.V | Key.CtrlMask:
case Key.PageDown:
- MoveDown (bytesPerLine * Frame.Height);
- break;
+ return MoveDown (bytesPerLine * Frame.Height);
case Key.Home:
- DisplayStart = 0;
- SetNeedsDisplay ();
- break;
+ return MoveHome ();
+ case Key.End:
+ return MoveEnd ();
+ case Key.CursorLeft | Key.CtrlMask:
+ return MoveStartOfLine ();
+ case Key.CursorRight | Key.CtrlMask:
+ return MoveEndOfLine ();
+ case Key.CursorUp | Key.CtrlMask:
+ return MoveUp (bytesPerLine * ((int)(position - displayStart) / bytesPerLine));
+ case Key.CursorDown | Key.CtrlMask:
+ return MoveDown (bytesPerLine * (Frame.Height - 1 - ((int)(position - displayStart) / bytesPerLine)));
default:
+ if (!AllowEdits)
+ return false;
+
+ // Ignore control characters and other special keys
+ if (keyEvent.Key < Key.Space || keyEvent.Key > Key.CharMask)
+ return false;
+
if (leftSide) {
- int value = -1;
+ int value;
var k = (char)keyEvent.Key;
if (k >= 'A' && k <= 'F')
value = k - 'A' + 10;
@@ -354,18 +447,89 @@ namespace Terminal.Gui {
RedisplayLine (position);
if (firstNibble) {
firstNibble = false;
- b = (byte)(b & 0xf | (value << 4));
+ b = (byte)(b & 0xf | (value << bsize));
edits [position] = b;
+ OnEdited (new KeyValuePair (position, edits [position]));
} else {
b = (byte)(b & 0xf0 | value);
edits [position] = b;
- CursorRight ();
+ OnEdited (new KeyValuePair (position, edits [position]));
+ MoveRight ();
}
return true;
} else
return false;
}
- PositionCursor ();
+ }
+
+ ///
+ /// Method used to invoke the event passing the .
+ ///
+ /// The key value pair.
+ public virtual void OnEdited (KeyValuePair keyValuePair)
+ {
+ Edited?.Invoke (keyValuePair);
+ }
+
+ ///
+ /// Method used to invoke the event passing the arguments.
+ ///
+ public virtual void OnPositionChanged ()
+ {
+ PositionChanged?.Invoke (new HexViewEventArgs (Position, CursorPosition, BytesPerLine));
+ }
+
+ ///
+ public override bool MouseEvent (MouseEvent me)
+ {
+ if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
+ && !me.Flags.HasFlag (MouseFlags.WheeledDown) && !me.Flags.HasFlag (MouseFlags.WheeledUp))
+ return false;
+
+ if (!HasFocus)
+ SetFocus ();
+
+ if (me.Flags == MouseFlags.WheeledDown) {
+ DisplayStart = Math.Min (DisplayStart + bytesPerLine, source.Length);
+ return true;
+ }
+
+ if (me.Flags == MouseFlags.WheeledUp) {
+ DisplayStart = Math.Max (DisplayStart - bytesPerLine, 0);
+ return true;
+ }
+
+ if (me.X < displayWidth)
+ return true;
+ var nblocks = bytesPerLine / bsize;
+ var blocksSize = nblocks * 14;
+ var blocksRightOffset = displayWidth + blocksSize - 1;
+ if (me.X > blocksRightOffset + bytesPerLine - 1)
+ return true;
+ leftSide = me.X >= blocksRightOffset;
+ var lineStart = (me.Y * bytesPerLine) + displayStart;
+ var x = me.X - displayWidth + 1;
+ var block = x / 14;
+ x -= block * 2;
+ var empty = x % 3;
+ var item = x / 3;
+ if (!leftSide && item > 0 && (empty == 0 || x == (block * 14) + 14 - 1 - (block * 2)))
+ return true;
+ firstNibble = true;
+ if (leftSide)
+ position = Math.Min (lineStart + me.X - blocksRightOffset, source.Length);
+ else
+ position = Math.Min (lineStart + item, source.Length);
+
+ if (me.Flags == MouseFlags.Button1DoubleClicked) {
+ leftSide = !leftSide;
+ if (leftSide)
+ firstNibble = empty == 1;
+ else
+ firstNibble = true;
+ }
+ SetNeedsDisplay ();
+
return true;
}
@@ -374,7 +538,7 @@ namespace Terminal.Gui {
/// of the underlying .
///
/// true if allow edits; otherwise, false.
- public bool AllowEdits { get; set; }
+ public bool AllowEdits { get; set; } = true;
///
/// Gets a describing the edits done to the .
@@ -384,16 +548,56 @@ namespace Terminal.Gui {
public IReadOnlyDictionary Edits => edits;
///
- /// This method applies andy edits made to the and resets the
- /// contents of the property
+ /// Gets the current character position starting at one, related to the .
///
- public void ApplyEdits ()
+ public long Position => position + 1;
+
+ ///
+ /// Gets the current cursor position starting at one for both, line and column.
+ ///
+ public Point CursorPosition {
+ get {
+ var delta = (int)position;
+ var line = delta / bytesPerLine + 1;
+ var item = delta % bytesPerLine + 1;
+
+ return new Point (item, line);
+ }
+ }
+
+ ///
+ /// The bytes length per line.
+ ///
+ public int BytesPerLine => bytesPerLine;
+
+ ///
+ /// This method applies and edits made to the and resets the
+ /// contents of the property.
+ ///
+ /// If provided also applies the changes to the passed .
+ public void ApplyEdits (Stream stream = null)
{
foreach (var kv in edits) {
source.Position = kv.Key;
source.WriteByte (kv.Value);
+ source.Flush ();
+ if (stream != null) {
+ stream.Position = kv.Key;
+ stream.WriteByte (kv.Value);
+ stream.Flush ();
+ }
}
edits = new SortedDictionary ();
+ SetNeedsDisplay ();
+ }
+
+ ///
+ /// This method discards the edits made to the by resetting the
+ /// contents of the property.
+ ///
+ public void DiscardEdits ()
+ {
+ edits = new SortedDictionary ();
}
private CursorVisibility desiredCursorVisibility = CursorVisibility.Default;
@@ -411,5 +615,45 @@ namespace Terminal.Gui {
desiredCursorVisibility = value;
}
}
+
+ ///
+ public override bool OnEnter (View view)
+ {
+ Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
+
+ return base.OnEnter (view);
+ }
+
+ ///
+ /// Defines the event arguments for event.
+ ///
+ public class HexViewEventArgs : EventArgs {
+ ///
+ /// Gets the current character position starting at one, related to the .
+ ///
+ public long Position { get; private set; }
+ ///
+ /// Gets the current cursor position starting at one for both, line and column.
+ ///
+ public Point CursorPosition { get; private set; }
+
+ ///
+ /// The bytes length per line.
+ ///
+ public int BytesPerLine { get; private set; }
+
+ ///
+ /// Initializes a new instance of
+ ///
+ /// The character position.
+ /// The cursor position.
+ /// Line bytes length.
+ public HexViewEventArgs (long pos, Point cursor, int lineLength)
+ {
+ Position = pos;
+ CursorPosition = cursor;
+ BytesPerLine = lineLength;
+ }
+ }
}
}
diff --git a/UICatalog/Scenarios/HexEditor.cs b/UICatalog/Scenarios/HexEditor.cs
index f48b6c241..b3dc3d488 100644
--- a/UICatalog/Scenarios/HexEditor.cs
+++ b/UICatalog/Scenarios/HexEditor.cs
@@ -14,13 +14,26 @@ namespace UICatalog.Scenarios {
private string _fileName = "demo.bin";
private HexView _hexView;
private bool _saved = true;
+ private MenuItem miAllowEdits;
+ private StatusItem siPositionChanged;
+ private StatusBar statusBar;
public override void Setup ()
{
- Win.Title = this.GetName() + "-" + _fileName ?? "Untitled";
- Win.Y = 1; // menu
- Win.Height = Dim.Fill (1); // status bar
- Top.LayoutSubviews ();
+ Win.Title = this.GetName () + "-" + _fileName ?? "Untitled";
+
+ CreateDemoFile (_fileName);
+ //CreateUnicodeDemoFile (_fileName);
+
+ _hexView = new HexView (LoadFile ()) {
+ X = 0,
+ Y = 0,
+ Width = Dim.Fill (),
+ Height = Dim.Fill (),
+ };
+ _hexView.Edited += _hexView_Edited;
+ _hexView.PositionChanged += _hexView_PositionChanged;
+ Win.Add (_hexView);
var menu = new MenuBar (new MenuBarItem [] {
new MenuBarItem ("_File", new MenuItem [] {
@@ -35,48 +48,61 @@ namespace UICatalog.Scenarios {
new MenuItem ("C_ut", "", () => Cut()),
new MenuItem ("_Paste", "", () => Paste())
}),
+ new MenuBarItem ("_Options", new MenuItem [] {
+ miAllowEdits = new MenuItem ("_AllowEdits", "", () => ToggleAllowEdits ()){Checked = _hexView.AllowEdits, CheckType = MenuItemCheckStyle.Checked}
+ })
});
Top.Add (menu);
- var statusBar = new StatusBar (new StatusItem [] {
- //new StatusItem(Key.Enter, "~ENTER~ ApplyEdits", () => { _hexView.ApplyEdits(); }),
+ statusBar = new StatusBar (new StatusItem [] {
new StatusItem(Key.F2, "~F2~ Open", () => Open()),
new StatusItem(Key.F3, "~F3~ Save", () => Save()),
new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
+ siPositionChanged = new StatusItem(Key.Null,
+ $"Position: {_hexView.Position} Line: {_hexView.CursorPosition.Y} Col: {_hexView.CursorPosition.X} Line length: {_hexView.BytesPerLine}", () => {})
});
Top.Add (statusBar);
+ }
- CreateDemoFile (_fileName);
+ private void _hexView_PositionChanged (HexView.HexViewEventArgs obj)
+ {
+ siPositionChanged.Title = $"Position: {obj.Position} Line: {obj.CursorPosition.Y} Col: {obj.CursorPosition.X} Line length: {obj.BytesPerLine}";
+ statusBar.SetNeedsDisplay ();
+ }
- _hexView = new HexView (LoadFile()) {
- X = 0,
- Y = 0,
- Width = Dim.Fill (),
- Height = Dim.Fill (),
- };
- _hexView.CanFocus = true;
- Win.Add (_hexView);
+ private void ToggleAllowEdits ()
+ {
+ _hexView.AllowEdits = miAllowEdits.Checked = !miAllowEdits.Checked;
+ }
+
+ private void _hexView_Edited (System.Collections.Generic.KeyValuePair obj)
+ {
+ _saved = false;
}
private void New ()
{
_fileName = null;
- Win.Title = this.GetName () + "-" + _fileName ?? "Untitled";
- throw new NotImplementedException ();
+ _hexView.Source = LoadFile ();
}
private Stream LoadFile ()
{
- MemoryStream stream = null;
- if (!_saved) {
- MessageBox.ErrorQuery ("Not Implemented", "Functionality not yet implemented.", "Ok");
+ MemoryStream stream = new MemoryStream ();
+ if (!_saved && _hexView != null && _hexView.Edits.Count > 0) {
+ if (MessageBox.ErrorQuery ("Save", "The changes were not saved. Want to open without saving?", "Yes", "No") == 1)
+ return _hexView.Source;
+ _hexView.DiscardEdits ();
+ _saved = true;
}
if (_fileName != null) {
var bin = System.IO.File.ReadAllBytes (_fileName);
- stream = new MemoryStream (bin);
+ stream.Write (bin);
Win.Title = this.GetName () + "-" + _fileName;
_saved = true;
+ } else {
+ Win.Title = this.GetName () + "-" + (_fileName ?? "Untitled");
}
return stream;
}
@@ -94,9 +120,6 @@ namespace UICatalog.Scenarios {
private void Copy ()
{
MessageBox.ErrorQuery ("Not Implemented", "Functionality not yet implemented.", "Ok");
- //if (_textView != null && _textView.SelectedLength != 0) {
- // _textView.Copy ();
- //}
}
private void Open ()
@@ -115,11 +138,14 @@ namespace UICatalog.Scenarios {
{
if (_fileName != null) {
using (FileStream fs = new FileStream (_fileName, FileMode.OpenOrCreate)) {
- _hexView.ApplyEdits ();
- _hexView.Source.CopyTo (fs);
- fs.Flush ();
+ _hexView.ApplyEdits (fs);
+ //_hexView.Source.Position = 0;
+ //_hexView.Source.CopyTo (fs);
+ //fs.Flush ();
}
_saved = true;
+ } else {
+ _hexView.ApplyEdits ();
}
}
@@ -128,10 +154,9 @@ namespace UICatalog.Scenarios {
Application.RequestStop ();
}
- private void CreateDemoFile(string fileName)
+ private void CreateDemoFile (string fileName)
{
var sb = new StringBuilder ();
- // BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
sb.Append ("Hello world.\n");
sb.Append ("This is a test of the Emergency Broadcast System.\n");
@@ -139,5 +164,19 @@ namespace UICatalog.Scenarios {
sw.Write (sb.ToString ());
sw.Close ();
}
+
+ private void CreateUnicodeDemoFile (string fileName)
+ {
+ var sb = new StringBuilder ();
+ sb.Append ("Hello world.\n");
+ sb.Append ("This is a test of the Emergency Broadcast System.\n");
+
+ byte [] buffer = Encoding.Unicode.GetBytes (sb.ToString());
+ MemoryStream ms = new MemoryStream (buffer);
+ FileStream file = new FileStream (fileName, FileMode.Create, FileAccess.Write);
+ ms.WriteTo (file);
+ file.Close ();
+ ms.Close ();
+ }
}
}
diff --git a/UnitTests/HexViewTests.cs b/UnitTests/HexViewTests.cs
new file mode 100644
index 000000000..89d1dbaf8
--- /dev/null
+++ b/UnitTests/HexViewTests.cs
@@ -0,0 +1,399 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Terminal.Gui.Views {
+ public class HexViewTests {
+ [Fact]
+ public void Constructors_Defaults ()
+ {
+ var hv = new HexView ();
+ Assert.NotNull (hv.Source);
+ Assert.IsAssignableFrom (hv.Source);
+ Assert.True (hv.CanFocus);
+ Assert.True (hv.AllowEdits);
+
+ hv = new HexView (new System.IO.MemoryStream ());
+ Assert.NotNull (hv.Source);
+ Assert.IsAssignableFrom (hv.Source);
+ Assert.True (hv.CanFocus);
+ Assert.True (hv.AllowEdits);
+ }
+
+ private Stream LoadStream (bool unicode = false)
+ {
+ MemoryStream stream = new MemoryStream ();
+ byte [] bArray;
+ string memString = "Hello world.\nThis is a test of the Emergency Broadcast System.\n";
+
+ Assert.Equal (63, memString.Length);
+
+ if (unicode) {
+ bArray = Encoding.Unicode.GetBytes (memString);
+ Assert.Equal (126, bArray.Length);
+ } else {
+ bArray = Encoding.Default.GetBytes (memString);
+ Assert.Equal (63, bArray.Length);
+ }
+ stream.Write (bArray);
+
+ return stream;
+ }
+
+ [Fact]
+ public void AllowEdits_Edits_ApplyEdits ()
+ {
+ var hv = new HexView (LoadStream (true)) {
+ Width = 20,
+ Height = 20
+ };
+
+ Assert.Empty (hv.Edits);
+ hv.AllowEdits = false;
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.Home, new KeyModifiers ())));
+ Assert.False (hv.ProcessKey (new KeyEvent (Key.A, new KeyModifiers ())));
+ Assert.Empty (hv.Edits);
+ Assert.Equal (126, hv.Source.Length);
+
+ hv.AllowEdits = true;
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers ())));
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers ())));
+ Assert.Single (hv.Edits);
+ Assert.Equal (65, hv.Edits.ToList () [0].Value);
+ Assert.Equal ('A', (char)hv.Edits.ToList () [0].Value);
+ Assert.Equal (126, hv.Source.Length);
+
+ // Appends byte
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.End, new KeyModifiers ())));
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers ())));
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.D2, new KeyModifiers ())));
+ Assert.Equal (2, hv.Edits.Count);
+ Assert.Equal (66, hv.Edits.ToList () [1].Value);
+ Assert.Equal ('B', (char)hv.Edits.ToList () [1].Value);
+ Assert.Equal (126, hv.Source.Length);
+
+ hv.ApplyEdits ();
+ Assert.Empty (hv.Edits);
+ Assert.Equal (127, hv.Source.Length);
+ }
+
+ [Fact]
+ public void DisplayStart_Source ()
+ {
+ var hv = new HexView (LoadStream (true)) {
+ Width = 20,
+ Height = 20
+ };
+
+ Assert.Equal (0, hv.DisplayStart);
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ())));
+ Assert.Equal (4 * hv.Frame.Height, hv.DisplayStart);
+ Assert.Equal (hv.Source.Length, hv.Source.Position);
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.End, new KeyModifiers ())));
+ // already on last page and so the DisplayStart is the same as before
+ Assert.Equal (4 * hv.Frame.Height, hv.DisplayStart);
+ Assert.Equal (hv.Source.Length, hv.Source.Position);
+ }
+
+ [Fact]
+ public void Edited_Event ()
+ {
+ var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+ KeyValuePair keyValuePair = default;
+ hv.Edited += (e) => keyValuePair = e;
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers ())));
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.D6, new KeyModifiers ())));
+
+ Assert.Equal (0, (int)keyValuePair.Key);
+ Assert.Equal (70, (int)keyValuePair.Value);
+ Assert.Equal ('F', (char)keyValuePair.Value);
+ }
+
+ [Fact]
+ public void DiscardEdits_Method ()
+ {
+ var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers ())));
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.D1, new KeyModifiers ())));
+ Assert.Single (hv.Edits);
+ Assert.Equal (65, hv.Edits.ToList () [0].Value);
+ Assert.Equal ('A', (char)hv.Edits.ToList () [0].Value);
+ Assert.Equal (126, hv.Source.Length);
+
+ hv.DiscardEdits ();
+ Assert.Empty (hv.Edits);
+ }
+
+ [Fact]
+ public void Position_Using_Encoding_Unicode ()
+ {
+ var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+ Assert.Equal (126, hv.Source.Length);
+ Assert.Equal (126, hv.Source.Position);
+ Assert.Equal (1, hv.Position);
+
+ // left side needed to press twice
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+ Assert.Equal (126, hv.Source.Position);
+ Assert.Equal (1, hv.Position);
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+ Assert.Equal (126, hv.Source.Position);
+ Assert.Equal (2, hv.Position);
+
+ // right side only needed to press one time
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+ Assert.Equal (126, hv.Source.Position);
+ Assert.Equal (2, hv.Position);
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
+ Assert.Equal (126, hv.Source.Position);
+ Assert.Equal (1, hv.Position);
+
+ // last position is equal to the source length
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.End, new KeyModifiers ())));
+ Assert.Equal (126, hv.Source.Position);
+ Assert.Equal (127, hv.Position);
+ Assert.Equal (hv.Position - 1, hv.Source.Length);
+ }
+
+ [Fact]
+ public void Position_Using_Encoding_Default ()
+ {
+ var hv = new HexView (LoadStream ()) { Width = 20, Height = 20 };
+ Assert.Equal (63, hv.Source.Length);
+ Assert.Equal (63, hv.Source.Position);
+ Assert.Equal (1, hv.Position);
+
+ // left side needed to press twice
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+ Assert.Equal (63, hv.Source.Position);
+ Assert.Equal (1, hv.Position);
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+ Assert.Equal (63, hv.Source.Position);
+ Assert.Equal (2, hv.Position);
+
+ // right side only needed to press one time
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+ Assert.Equal (63, hv.Source.Position);
+ Assert.Equal (2, hv.Position);
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
+ Assert.Equal (63, hv.Source.Position);
+ Assert.Equal (1, hv.Position);
+
+ // last position is equal to the source length
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.End, new KeyModifiers ())));
+ Assert.Equal (63, hv.Source.Position);
+ Assert.Equal (64, hv.Position);
+ Assert.Equal (hv.Position - 1, hv.Source.Length);
+ }
+
+ [Fact]
+ [AutoInitShutdown]
+ public void CursorPosition_Encoding_Unicode ()
+ {
+ var hv = new HexView (LoadStream (true)) { Width = Dim.Fill (), Height = Dim.Fill () };
+ Application.Top.Add (hv);
+ Application.Begin (Application.Top);
+
+ Assert.Equal (new Point (1, 1), hv.CursorPosition);
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask, new KeyModifiers ())));
+ Assert.Equal (hv.CursorPosition.X, hv.BytesPerLine);
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.Home, new KeyModifiers ())));
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+ Assert.Equal (new Point (2, 1), hv.CursorPosition);
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+ Assert.Equal (new Point (2, 2), hv.CursorPosition);
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.End, new KeyModifiers ())));
+ var col = hv.CursorPosition.X;
+ var line = hv.CursorPosition.Y;
+ var offset = (line - 1) * (hv.BytesPerLine - col);
+ Assert.Equal (hv.Position, col * line + offset);
+ }
+
+ [Fact]
+ [AutoInitShutdown]
+ public void CursorPosition_Encoding_Default ()
+ {
+ var hv = new HexView (LoadStream ()) { Width = Dim.Fill (), Height = Dim.Fill () };
+ Application.Top.Add (hv);
+ Application.Begin (Application.Top);
+
+ Assert.Equal (new Point (1, 1), hv.CursorPosition);
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorRight | Key.CtrlMask, new KeyModifiers ())));
+ Assert.Equal (hv.CursorPosition.X, hv.BytesPerLine);
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.Home, new KeyModifiers ())));
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+ Assert.Equal (new Point (2, 1), hv.CursorPosition);
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+ Assert.Equal (new Point (2, 2), hv.CursorPosition);
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.End, new KeyModifiers ())));
+ var col = hv.CursorPosition.X;
+ var line = hv.CursorPosition.Y;
+ var offset = (line - 1) * (hv.BytesPerLine - col);
+ Assert.Equal (hv.Position, col * line + offset);
+ }
+
+ [Fact]
+ [AutoInitShutdown]
+ public void PositionChanged_Event ()
+ {
+ var hv = new HexView (LoadStream ()) { Width = Dim.Fill (), Height = Dim.Fill () };
+ HexView.HexViewEventArgs hexViewEventArgs = null;
+ hv.PositionChanged += (e) => hexViewEventArgs = e;
+ Application.Top.Add (hv);
+ Application.Begin (Application.Top);
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ()))); // left side must press twice
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+
+ Assert.Equal (12, hexViewEventArgs.BytesPerLine);
+ Assert.Equal (new Point (2, 2), hexViewEventArgs.CursorPosition);
+ Assert.Equal (14, hexViewEventArgs.Position);
+ }
+
+ private class NonSeekableStream : Stream {
+ Stream m_stream;
+ public NonSeekableStream (Stream baseStream)
+ {
+ m_stream = baseStream;
+ }
+ public override bool CanRead {
+ get { return m_stream.CanRead; }
+ }
+
+ public override bool CanSeek {
+ get { return false; }
+ }
+
+ public override bool CanWrite {
+ get { return m_stream.CanWrite; }
+ }
+
+ public override void Flush ()
+ {
+ m_stream.Flush ();
+ }
+
+ public override long Length {
+ get { throw new NotSupportedException (); }
+ }
+
+ public override long Position {
+ get {
+ return m_stream.Position;
+ }
+ set {
+ throw new NotSupportedException ();
+ }
+ }
+
+ public override int Read (byte [] buffer, int offset, int count)
+ {
+ return m_stream.Read (buffer, offset, count);
+ }
+
+ public override long Seek (long offset, SeekOrigin origin)
+ {
+ throw new NotImplementedException ();
+ }
+
+ public override void SetLength (long value)
+ {
+ throw new NotSupportedException ();
+ }
+
+ public override void Write (byte [] buffer, int offset, int count)
+ {
+ m_stream.Write (buffer, offset, count);
+ }
+ }
+
+ [Fact]
+ public void Exceptions_Tests ()
+ {
+ Assert.Throws (() => new HexView (null));
+ Assert.Throws (() => new HexView (new NonSeekableStream (new MemoryStream ())));
+ }
+
+ [Fact]
+ [AutoInitShutdown]
+ public void Source_Sets_DisplayStart_And_Position_To_Zero_If_Greater_Than_Source_Length ()
+ {
+ var hv = new HexView (LoadStream ()) { Width = 10, Height = 5 };
+ Application.Top.Add (hv);
+ Application.Begin (Application.Top);
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.End, new KeyModifiers ())));
+ Assert.Equal (62, hv.DisplayStart);
+ Assert.Equal (64, hv.Position);
+
+ hv.Source = new MemoryStream ();
+ Assert.Equal (0, hv.DisplayStart);
+ Assert.Equal (0, hv.Position - 1);
+
+ hv.Source = LoadStream ();
+ hv.Width = Dim.Fill ();
+ hv.Height = Dim.Fill ();
+ Application.Top.LayoutSubviews ();
+ Assert.Equal (0, hv.DisplayStart);
+ Assert.Equal (0, hv.Position - 1);
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.End, new KeyModifiers ())));
+ Assert.Equal (0, hv.DisplayStart);
+ Assert.Equal (64, hv.Position);
+
+ hv.Source = new MemoryStream ();
+ Assert.Equal (0, hv.DisplayStart);
+ Assert.Equal (0, hv.Position - 1);
+ }
+
+ [Fact]
+ public void ApplyEdits_With_Argument ()
+ {
+ byte [] buffer = Encoding.Default.GetBytes ("Fest");
+ var original = new MemoryStream ();
+ original.Write (buffer, 0, buffer.Length);
+ original.Flush ();
+ var copy = new MemoryStream ();
+ original.Position = 0;
+ original.CopyTo (copy);
+ copy.Flush ();
+ var hv = new HexView (copy) { Width = Dim.Fill (), Height = Dim.Fill () };
+ byte [] readBuffer = new byte [hv.Source.Length];
+ hv.Source.Position = 0;
+ hv.Source.Read (readBuffer);
+ Assert.Equal ("Fest", Encoding.Default.GetString (readBuffer));
+
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.D5, new KeyModifiers ())));
+ Assert.True (hv.ProcessKey (new KeyEvent (Key.D4, new KeyModifiers ())));
+ readBuffer [hv.Edits.ToList () [0].Key] = hv.Edits.ToList () [0].Value;
+ Assert.Equal ("Test", Encoding.Default.GetString (readBuffer));
+
+ hv.ApplyEdits (original);
+ original.Position = 0;
+ original.Read (buffer);
+ copy.Position = 0;
+ copy.Read (readBuffer);
+ Assert.Equal ("Test", Encoding.Default.GetString (buffer));
+ Assert.Equal ("Test", Encoding.Default.GetString (readBuffer));
+ Assert.Equal (Encoding.Default.GetString (buffer), Encoding.Default.GetString (readBuffer));
+ }
+ }
+}
diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj
index d56bf8b73..62a83c34b 100644
--- a/UnitTests/UnitTests.csproj
+++ b/UnitTests/UnitTests.csproj
@@ -16,7 +16,7 @@
-
+