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; } }