Intelliprompt Questions

SyntaxEditor for WPF Forum

Posted 10 years ago by John Dunn
Version: 9.2.0515
Avatar
I have 2 Intelliprompt questions that I haven't been able to figure out. For context, this is using lua as the language.

First, I want to show a list of globals when the user starts typing a new word. I though using e.IsTypedWordStart would be the correct thing to do. This is true when starting in whitespace but it is also true if the user types a non-global token, a '.' and then types a character. If user types a global token ( like math ), when they hit '.' I then display the math Session ( which is working correctly ). But if the first token is not a global I don't display anything and then I end up displaying my global when the character after the '.' is typed.

Here's my DocumentTextChanged function. _SessionDict is a Dictionary<string,CompletionSession>.

    void _Code_DocumentTextChanged(object sender, EditorSnapshotChangedEventArgs e)
    {
      if (_Code.IntelliPrompt.Sessions.Any(s => s.IsOpen)) return;

      ITextSnapshotReader reader = _Code.ActiveView.GetReader();
      IMergableToken im = reader.ReadTokenReverse() as IMergableToken;
      // don't show popup when typing a string or a comment
      if (im != null && im.ClassificationType != null)
      {
        if (im.ClassificationType.Key == "String" || im.ClassificationType.Key == "Comment")
        {
          return;
        }
      }
      switch (e.TypedText)
      {
        case ".":
          {
            if (im != null)
            {
              reader.ReadCharacterReverseThrough('.');
              string tok = reader.TokenText;
              CompletionSession session;
              if (_SessionDict.TryGetValue(reader.TokenText, out session))
              {
                session.Open(_Code.ActiveView);
              }
            }
            return;
          }
      }
      if (e.IsTypedWordStart)
      {
        _GlobalSession.Open(_Code.ActiveView);
      }
    }
My second question is a little more involved. There are some custom tables we place in the lua environment. The easiest way to describe them is probably as classes

class Control
{
  public double Value { get; set; }
  public double Position { get; set; }
}
static class Controls 
{
  public Control[] Inputs;
  public Control[] Outputs;
}
So the user can type

Controls.Inputs[2].Value = 3
Assuming I can solve my first question, showing 'Controls' as a choice when the user starts typing a new token is trivial. If they type 'Controls.', I can then display either 'Inputs' or 'Outputs' the same way I am displaying the math table methods ( this is assuming that the above way is the correct way to do that ). The final thing I need to do is to detected 'Controls.Inputs[<somenumber>].' and display 'Value' and 'Position'. Is there a built in mechanism that would allow me to accomplish this?

Comments (3)

Posted 10 years ago by John Dunn
Avatar
I've improved my approach but I'm not sure (1) it's correct and (2) if so, if there's not a better way to accomplish what I am trying.

First, I added some entries to Lua.langdef

    <ClassificationType Key="QSysControlFunction" DefaultStyle="#FF0080FF" />
...
        <RegexPatternGroup
          TokenKey="QSysControlFunction"
          ClassificationTypeKey="QSysControlFunction"
          Pattern="Controls.(Inputs|Outputs)\[[\d]\].(Position|Value|String)" />
        <RegexPatternGroup 
          TokenKey="QSysControlVariable" 
          ClassificationTypeKey="QSysControlVariable" 
          Pattern="Controls.(Inputs|Outputs)\[[\d]\]." />
And then changed my DocumentTextChanged to

    bool IsWhiteSpacePrevious(ITextSnapshotReader reader)
    {
      if (
        reader.ReadTokenReverse() == null ||
        reader.Token == null ||
        reader.Token.Key == "Whitespace" ||
        reader.Token.Key == "LineTerminator" ||
        reader.Token.Key == "OpenParenthesis" ||
        reader.Token.Key == "CloseParenthesis" ||
        reader.Token.Key == "OpenCurlyBrace" ||
        reader.Token.Key == "CloseCurlyBrace" ||
        reader.Token.Key == "OpenSquareBrace" ||
        reader.Token.Key == "CloseSquareBrace" ||
        reader.Token.Key == "Operator" ||
        reader.Token.Key == "Operator") return true;

      if (reader.Token.Key == "Punctuation")
      {
        if (reader.TokenText == ";" || reader.TokenText == ",") return true;
      }
      return false;
    }
    void _Code_DocumentTextChanged(object sender, EditorSnapshotChangedEventArgs e)
    {
      if (_Code.IntelliPrompt.Sessions.Any(s => s.IsOpen)) return;

      ITextSnapshotReader reader = _Code.ActiveView.GetReader();
      IMergableToken im = reader.ReadTokenReverse() as IMergableToken;
      // don't show popup when typing a string or a comment
      if (im != null && im.ClassificationType != null)
      {
        if (im.ClassificationType.Key == "String" || im.ClassificationType.Key == "Comment") return;
      }
      if (reader.Token != null && reader.Token.Key == "QSysControlVariable")
      {
        _QSysControl.Open(_Code.ActiveView);
        return;
      }
      if (reader.TokenText == ".")
      {
        reader.ReadTokenReverse();
        CompletionSession session;
        if (_SessionDict.TryGetValue(reader.TokenText, out session))
        {
          // make sure we aren't inside another table
          if (IsWhiteSpacePrevious(reader))
          {
            session.Open(_Code.ActiveView);
          }
        }
        return;

      }
      if (e.IsTypedWordStart)
      {
        if (IsWhiteSpacePrevious(reader))
        {
          _GlobalSession.Open(_Code.ActiveView);
        }
      }
    }
This appears to work - at least in a short amount of testing. Assuming this is close to correct - I still have 2 questions.

First, is there a better way to implement IsWhiteSpacePrevious()?

Second, I'm still having issues with the syntax coloring. The QSysControlFunction Classification type ends up coloring the entire thing, including the []s and number. Ideally it would only color the text tokens and not the '.'s, []s or number. When I attempted to split up my Classification, it ending up breaking my detection of when the user typed the '.' after the Controls.Inputs[<digit>].
Posted 10 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi John,

Quick note for this:
if (_Code.IntelliPrompt.Sessions.Any(s => s.IsOpen)) return;

Keep in mind that quick info on mouse hovers are considered sessions so you may want to make that check more specific, like see if a completion session is open or not.

I may be wrong but the if statement for the "QSysControlVariable" area looks like it's in an incorrect location. Don't you want it in the 'if' to see if the typed text is a letter? Otherwise I would expect Del key presses etc could open it in some scenarios.

Every time you call reader.Token it's doing a quick perf hit to validate that the token hasn't changed, etc. You are better off doing a switch statement on token.Key in IsWhiteSpacePrevious.

For your syntax highlighting, correct because you now have a huge token and you can't break up classifications in a single token. So while having a large token will help making some of your session determination logic easier, you add the highlighting issue.

Once we get a grammar/AST model implemented in the coming months and start to port over our .NET Languages Add-on, we plan on possibly coming up with a generic mechanism that will parse an expression. So you'd tell this expression parser that your language divides its levels with '.' chars, give it the identifier token type, and tell it that '[' and ']' indicate indexers, etc. Then it ideally would start at a given offset (where the caret is) and move back to examine the tokens and build a data structure. So if our text was "Controls.Inputs[0]" the data structure would have two items, the first pointing to the token with "Controls" and the second to the token with "Input". The second item would also have some sort of recursion data structure that would contain the "0".

We do that sort of thing already in our .NET Languages Add-on for WinForms. For instance if you have "int.Parse" in SE for WinForms we make a context object with two item "int" and "Parse". We also resolve "int" to mean the Int32 type and "Parse" to be a member on that type. Then we pass our context object to code that builds IntelliPrompt popups. It knows to go reflect off the Int32.Parse method to look for parameters, etc.

Our thought is that if we made a generic mechanism, a lot of other languages could use it in the future. So when we port over the add-on, we do plan on looking into that sort of thing. But in the meantime you'd have to roll your own if you want to go that route.

Hope that helps.


Actipro Software Support

Posted 10 years ago by John Dunn
Avatar
Thanks, that did help. I think I'll wait until the the generic grammar/AST possibly shows up - what I have now works reasonably well right now.
The latest build of this product (v2019.1 build 0683) was released 1 month ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.