@ -6,6 +6,7 @@ using CommunityToolkit.Mvvm.Messaging;
using MultiTerm.Core.Model ;
using MultiTerm.Core.Types ;
using MultiTerm.Protocols.Model ;
using System.Collections.ObjectModel ;
using System.Text ;
namespace MultiTerm.Core.ViewModel ;
@ -16,11 +17,17 @@ public partial class ConsoleViewModel : TerminalViewModel
private readonly ILogger logger ;
private Encoding currentEncoding ;
private List < char > ? listOfPrevCharacters ;
/* settings */
private readonly Encoding defaultEncoding = Encoding . ASCII ; // default encoding instance
private const EncodingType defaultEncodingType = EncodingType . ASCII ; // default encoding type
private const int DataMaxLength = 5 0 0 0 ; // maximum amount of characters stored int the data textbox
private const int DataMaxLength = 2 0 0 0 0 ; // limit of maximum shown characters
/// <summary>
/// Newline Sequence string inserted on every newline in the <see cref="Data"/>.
/// </summary>
public static readonly string NewlineSequence = Environment . NewLine ;
#region Observable Properties
/// <summary>
@ -53,7 +60,9 @@ public partial class ConsoleViewModel : TerminalViewModel
// extract bytes
var bytes = newData . Select ( b = > b . Byte ) . ToArray ( ) ;
string newDataDecoded = currentEncoding . GetString ( bytes ) ;
// calculate new legth
// replace newlines
newDataDecoded = ReplaceNewlineCharacters ( newDataDecoded , ref this . listOfPrevCharacters , this . DataDisplayNewlineSeparatorType , currentEncoding ) ;
// calculate new lnegth
int newTotalLength = this . Data . Length + newDataDecoded . Length ;
// trim current data at the beginning if new length is too long
if ( newTotalLength > DataMaxLength )
@ -109,6 +118,158 @@ public partial class ConsoleViewModel : TerminalViewModel
this . currentEncoding = this . GetEncoding ( value ) ;
}
#region Newline Insertion
/// <summary>
/// Returns if the <paramref name="character"/> is a known newline characters.
/// </summary>
/// <param name="character">character to test if it is a known newline characters</param>
/// <returns>true if the character is a newline character</returns>
private static bool IsKnownNewlineCharacter ( char character )
{
// must be extended if new newline characters shall be detected!
const string knownNewlineCharacters = "\n\r" ;
return knownNewlineCharacters . Contains ( character ) ;
}
/// <summary>
/// Searches <paramref name="text"/> for known Newline characters (using <see cref="IsKnownNewlineCharacter(char)"/>. Removes all known Newline characters from the text.
/// If the newline sequence according to <paramref name="newlineSeparatorType"/> is found, the configured <see cref="NewlineSequence"/> is introduced.
/// </summary>
/// <param name="text">text to search for newline characters</param>
/// <param name="previousCharacters">list of previous characters, managed by this method. to identify newline sequences over multiple calls of this method</param>
/// <param name="newlineSeparatorType">separator type</param>
/// <param name="encoding">encoding to use</param>
/// <returns>string with replaced newline characters</returns>
private static string ReplaceNewlineCharacters (
string text ,
ref List < char > ? previousCharacters ,
NewlineSeparatorType newlineSeparatorType ,
Encoding encoding )
{
StringBuilder stringBuilder = new ( ) ;
// go through characters
foreach ( char character in text )
{
// add to collection if the character is not a known newline character
if ( IsKnownNewlineCharacter ( character ) = = false )
{
stringBuilder . Append ( character ) ;
}
// check if a newline should be introduced after this character
switch ( ShouldIntroduceNewlineAfterThisCharacter ( character , previousCharacters , newlineSeparatorType , encoding ) )
{
case ShouldIntroduceNewlineAfterThisCharacterResult . NoNewline :
// a decision could be made, previousCharacters can be cleared
if ( previousCharacters ! = null ) { previousCharacters = null ; }
// nothing to do
break ;
case ShouldIntroduceNewlineAfterThisCharacterResult . IntroduceNewline :
// a decision could be made, previousCharacters can be cleared
if ( previousCharacters ! = null ) { previousCharacters = null ; }
// append line in string
stringBuilder . Append ( NewlineSequence ) ;
break ;
case ShouldIntroduceNewlineAfterThisCharacterResult . RequiresMoreCharacters :
// first time that more characters are required => create new list
previousCharacters ? ? = new List < char > ( ) ;
// add current character to list
previousCharacters . Add ( character ) ;
break ;
default :
throw new Exception ( $"'{nameof(ReplaceNewlineCharacters)}()' failed because of error when checking if a newline should be introduced." ) ;
}
}
return stringBuilder . ToString ( ) ;
}
/// <summary>
/// Result type for <see cref="ShouldIntroduceNewlineAfterThisCharacter"/>
/// </summary>
public enum ShouldIntroduceNewlineAfterThisCharacterResult
{
/// <summary>
/// No newline is required.
/// </summary>
NoNewline ,
/// <summary>
/// A newline shall be introduced after this byte.
/// </summary>
IntroduceNewline ,
/// <summary>
/// Following characters are required to finalize result wether a newline shall be introduced or not.
/// </summary>
RequiresMoreCharacters
}
/// <summary>
/// Function to check wether a newline shall be introduced after the given <paramref name="character"/>.
/// Since some newline sequences will require multiple characters in correct order, a more complex handling is required, which is possible using this function.
/// </summary>
/// <param name="character">the current character in the collection</param>
/// <param name="previousCharacters">list of previous character, newest at the last position of the list, null if not required</param>
/// <param name="newlineSeparatorType">separator type</param>
/// <param name="encoding">encoding to be used to check if the characters are equal</param>
/// <returns>enum result of type <see cref="ShouldIntroduceNewlineAfterThisCharacterResult"/></returns>
/// <exception cref="NotImplementedException">if the handling for the <paramref name="newlineSeparatorType"/> is not implemented</exception>
private static ShouldIntroduceNewlineAfterThisCharacterResult ShouldIntroduceNewlineAfterThisCharacter (
char character ,
List < char > ? previousCharacters ,
NewlineSeparatorType newlineSeparatorType ,
Encoding encoding )
{
var result = ShouldIntroduceNewlineAfterThisCharacterResult . NoNewline ;
switch ( newlineSeparatorType )
{
case NewlineSeparatorType . None :
break ;
case NewlineSeparatorType . CR :
if ( EncodedCharacterEqualsAsciiCharacter ( character , '\r' , encoding ) )
{
result = ShouldIntroduceNewlineAfterThisCharacterResult . IntroduceNewline ;
}
break ;
case NewlineSeparatorType . LF :
if ( EncodedCharacterEqualsAsciiCharacter ( character , '\n' , encoding ) )
{
result = ShouldIntroduceNewlineAfterThisCharacterResult . IntroduceNewline ;
}
break ;
case NewlineSeparatorType . CR_LF :
if ( EncodedCharacterEqualsAsciiCharacter ( character , '\r' , encoding ) )
{
result = ShouldIntroduceNewlineAfterThisCharacterResult . RequiresMoreCharacters ;
}
if ( EncodedCharacterEqualsAsciiCharacter ( character , '\n' , encoding ) )
{
if ( previousCharacters ! = null & & EncodedCharacterEqualsAsciiCharacter ( previousCharacters . Last ( ) , '\r' , encoding ) )
{
result = ShouldIntroduceNewlineAfterThisCharacterResult . IntroduceNewline ;
}
}
break ;
default :
throw new NotImplementedException ( $"'{nameof(ShouldIntroduceNewlineAfterThisCharacter)}()' does not implement handling for {nameof(NewlineSeparatorType)} {newlineSeparatorType}" ) ;
}
return result ;
}
# endregion
#region Unused Methods
public override void DisplayNewSentData ( IEnumerable < ExtendedByte > newData )
{
@ -155,5 +316,15 @@ public partial class ConsoleViewModel : TerminalViewModel
return encoding ! ;
}
/// <summary>
/// Returns wether the <paramref name="encodedChar"/> equals the <paramref name="asciiChar"/>.
/// <paramref name="encodedChar"/> must be encoded with <paramref name="encoding"/>.
/// </summary>
/// <returns>true if equal</returns>
private static bool EncodedCharacterEqualsAsciiCharacter ( char encodedChar , char asciiChar , Encoding encoding )
{
return encodedChar . ToString ( ) = = encoding . GetString ( Encoding . ASCII . GetBytes ( new char [ ] { asciiChar } ) ) ;
}
# endregion
}