diff --git a/Bonsai.Design/ObjectTextVisualizer.cs b/Bonsai.Design/ObjectTextVisualizer.cs
index 1c6de38e0..9cb204438 100644
--- a/Bonsai.Design/ObjectTextVisualizer.cs
+++ b/Bonsai.Design/ObjectTextVisualizer.cs
@@ -3,9 +3,10 @@
using System.Windows.Forms;
using Bonsai;
using Bonsai.Design;
+using System.Diagnostics;
using System.Drawing;
using System.Reactive;
-using System.Text.RegularExpressions;
+using System.Text;
[assembly: TypeVisualizer(typeof(ObjectTextVisualizer), Target = typeof(object))]
@@ -30,27 +31,87 @@ public class ObjectTextVisualizer : BufferedVisualizer
///
protected override void ShowBuffer(IList> values)
{
- if (values.Count > 0)
+ if (values.Count == 0)
+ return;
+
+ var sb = new StringBuilder();
+
+ // Add new values to the buffer (and only the ones which might appear)
+ for (int i = Math.Max(0, values.Count - bufferSize); i < values.Count; i++)
+ {
+ var rawText = values[i].Value?.ToString() ?? string.Empty;
+ sb.Clear();
+ sb.EnsureCapacity(rawText.Length + rawText.Length / 16); // Arbitrary extra space for control characters
+ MakeDisplayString(rawText, sb);
+ DisplayString(sb.ToString());
+ }
+
+ // Update the visual representation of the buffer
+ Debug.Assert(buffer.Count <= bufferSize);
+ sb.Clear();
+ foreach (var line in buffer)
{
- base.ShowBuffer(values);
- textBox.Text = string.Join(Environment.NewLine, buffer);
- textPanel.Invalidate();
+ if (sb.Length > 0)
+ sb.Append(Environment.NewLine);
+ sb.Append(line);
}
+ textBox.Text = sb.ToString();
+ textPanel.Invalidate();
}
///
public override void Show(object value)
{
- value ??= string.Empty;
- var text = value.ToString();
- text = Regex.Replace(text, @"\r|\n", string.Empty);
- buffer.Enqueue(text);
- while (buffer.Count > bufferSize)
+ var rawText = value?.ToString() ?? string.Empty;
+ var stringBuilder = new StringBuilder(rawText.Length);
+ MakeDisplayString(rawText, stringBuilder);
+ DisplayString(stringBuilder.ToString());
+ }
+
+ /// Converts the specified string for display in this visualizer
+ /// The raw text to be converted
+ /// The string builder which receives the display string
+ protected virtual void MakeDisplayString(string rawText, StringBuilder stringBuilder)
+ {
+ foreach (char c in rawText)
{
- buffer.Dequeue();
+ switch (c)
+ {
+ // Carriage returns are presumed to be followed by line feeds, skip them entirely
+ case '\r':
+ continue;
+ // Newlines become space so that things like multi-line JSON or matricies are still visible
+ case '\n':
+ case '\x0085': // Next line character (NEL)
+ case '\x2028': // Unicode line separator
+ case '\x2029': // Unicode paragraph separator
+ stringBuilder.Append(' ');
+ break;
+ case '\t':
+ stringBuilder.Append(c);
+ break;
+ // Replace all other control characters with the unknown replacement character
+ case < ' ': // C0 control characters
+ case '\x007F': // Delete
+ case >= '\x0080' and <= '\x009F': // C1 control characters
+ stringBuilder.Append('\xFFFD');
+ break;
+ default:
+ stringBuilder.Append(c);
+ break;
+ }
}
}
+ private void DisplayString(string value)
+ {
+ // Loop instead of simple if because the buffer size may have decreased
+ while (buffer.Count >= bufferSize)
+ buffer.Dequeue();
+
+ buffer.Enqueue(value);
+ }
+
///
public override void Load(IServiceProvider provider)
{
@@ -106,7 +167,9 @@ public override void Unload()
{
bufferSize = 0;
textBox.Dispose();
+ textPanel.Dispose();
textBox = null;
+ textPanel = null;
buffer = null;
}
}