1

Here is the code

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using System.Text.RegularExpressions;
public class syntaxHighlter : MonoBehaviour
{
    [SerializeField] private TMP_InputField inputField;
    [SerializeField] private TextMeshProUGUI outputField;
    string highlightedText;

    private void Start()
    {
        inputField.onValueChanged.AddListener(OnInputValueChanged);
    }

    private IEnumerator UpdateOutputField(string text)
    {
        yield return new WaitForSeconds(0.1f); // delay for 0.1 seconds
        outputField.text = text;
    }

    private void OnInputValueChanged(string text)
    {
        highlightedText = text.Replace("for", "<color=blue>for</color>")
            .Replace("int", "<color=green>int</color>")
            .Replace("float", "<color=green>float</color>")
            .Replace("bool", "<color=green>bool</color>")
            .Replace("void", "<color=green>void</color>");
        Debug.Log(highlightedText);
        
        StartCoroutine(UpdateOutputField(highlightedText));
    }
}

I'm trying to make syntaxHighliter in TMP input field, I want for example if I type the word "mov" it immediatly changes color to blue, but here apparently the outputfield.text is not changing when I modify it here. I tried changing directly inputfield but that created an infinite loop, tried modifying input field resulted in an infinite loop

shingo
  • 18,436
  • 5
  • 23
  • 42
  • What happens if you remove the listener, change the value, then add the listener back? – Retired Ninja Apr 19 '23 at 05:44
  • 1
    @RetiredNinja no need, there is [`SetTextWithoutNotify`](https://docs.unity3d.com/Packages/com.unity.textmeshpro@2.0/api/TMPro.TMP_InputField.html#TMPro_TMP_InputField_SetTextWithoutNotify_System_String_) – derHugo Apr 19 '23 at 07:46

1 Answers1

3

What you are doing would end up in an infinite loop of updates

  • a) because changing the text property again will invoke onValueChanged

    -> You rather want to use SetTextWithoutNotify here in order to not again invoke the onValueChanged

  • and b) because <color=green>int</color> also contains int and will with the next run result in <color=green><color=green>int</color></color> etc

-> you rather want to replace int only if it hasn't already been replaced before (=> wrapped in <color=green> and </color>).

So this should be fine

private void OnInputValueChanged(string text)
{
    text = text.Replace(" for ", "<color=blue>for</color>")
        .Replace(" int ", "<color=green>int</color>")
        .Replace(" float ", "<color=green>float</color>")
        .Replace(" bool ", "<color=green>bool</color>")
        .Replace(" void ", "<color=green>void</color>");

    Debug.Log(text);
    
    inputField.SetTextWithoutNotify(text);
}

There are also a couple of edge cases of course like e.g. having int as part of a member/variable etc name.

It might be more efficient and easier to rather go for Regex.Replace using e.g.

using System.Text.Regularxpressions;

...

const string pattern = @"(?<!<color=green>|[a-zA-Z0-9])(int|void|float|bool)(?!<\/color>|[a-zA-Z0-9])";
const string replacement = "<color=green>$1</color>";

...

inputField.SetTextWithoutNotify(Regex.Replace(input, pattern, replacement));

see .Net fiddle for demo and see Regex101 for further explanation how this regex works - here in short and simple

  • (int|void|float|bool) are the keywords you want to match. This is also the first and only Match Group
  • $1 later re-uses whatever was the actual content in this first match group
  • (?<!<color=green>|[a-zA-Z0-9]) this weird construct is a Negative Look-Behind. In simple it just means only match keywords if they do not come right after <color=green> or any alpha-numeric character
  • (?!<\/color>|[a-zA-Z0-9]) similar to above but a Negative Look-Ahead. In simple again only match keywords that are not followed by either </color> or any alpha-numeric character
derHugo
  • 83,094
  • 9
  • 75
  • 115
  • this works but there is a bug, the caret position doesnt change when for example i type "int (caretPostion)" when its replaced it becomes "int<\/color>" , the tag is invisible and changes the color of its content but somehow the caret points inside it – ryan trynda Apr 19 '23 at 14:18
  • have you tried setting the [`caretPosition`](https://docs.unity3d.com/Packages/com.unity.textmeshpro@1.3/api/TMPro.TMP_InputField.html#TMPro_TMP_InputField_caretPosition) then? I wouldn't claim that's a bug ... that's a new issue to tackle ;) – derHugo Apr 19 '23 at 14:28
  • yes i tried setting the caretPostion+some constant , when i did that for some reason the replace doesnt work – ryan trynda Apr 19 '23 at 14:33
  • update:it replaces the int but i cant move the caret further thant the end of "int" – ryan trynda Apr 19 '23 at 14:44