syntax editor

Posts   
 
    
billb
User
Posts: 50
Joined: 09-Jul-2004
# Posted on: 07-Jan-2005 20:17:50   

Here's one that's wayyyy off topic.

I noticed that TemplateStudio is using Actipro's control. I recently purchased a copy of that myself and I'm looking at doing some intellisense. I looked at the example program and it seemed a little rough.

Since my scripting language for my application will be C# and VB.NET, is it difficult to get the System.* libraries working in their control? I emailed their support and they told me it would take some work, which I'm not opposed to, but I wanted the opinion of someone who's done it. What was involved and how long did it take? Any tips?

Thanks.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 07-Jan-2005 20:54:51   

OK, here we GO! simple_smile (this is all in the texteditor control which is placed on each tab)

First some static caching stuff:


        private static Hashtable CachedTypesInAssemblies = new Hashtable();
        /// <summary>
        /// A hashtable with per partial name (for example System.Int16 will be stored here with key Int16) an arraylist with types which have this
        /// partial name as the last name part. The entries in the array list are type objects which are also present in CachedTypesInAssemblies
        /// </summary>
        private static Hashtable PartialTypeNameToCachedTypes = new Hashtable();


Then the static constructor:


        /// <summary>
        /// Static constructor, will fill cachedtypesinassemblies.
        /// </summary>
        static TextEditorMDIChild()
        {
            Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (Assembly assemblyData in assemblies) 
            {
                Type[] types = assemblyData.GetTypes();
                foreach(Type currentType in types)
                {
                    if(!currentType.IsPublic)
                    {
                        continue;
                    }
                    string fullName = currentType.FullName;
                    if(fullName.IndexOf('+') >=0)
                    {
                        // nested type
                        continue;
                    }
                    if(CachedTypesInAssemblies.ContainsKey(fullName))
                    {
                        continue;
                    }
                    CachedTypesInAssemblies.Add(fullName, currentType);
                    string[] fullNameInParts = fullName.Split('.');
                    ArrayList typesWithLastPart = null;
                    if(PartialTypeNameToCachedTypes.ContainsKey(fullNameInParts[fullNameInParts.Length-1]))
                    {
                        typesWithLastPart = (ArrayList)PartialTypeNameToCachedTypes[fullNameInParts[fullNameInParts.Length-1]];
                    }
                    else
                    {
                        typesWithLastPart = new ArrayList();
                    }
                    typesWithLastPart.Add(currentType);
                    PartialTypeNameToCachedTypes[fullNameInParts[fullNameInParts.Length-1]] = typesWithLastPart;
                }
            }
        }

then the trigger handlers: (you have to add triggers to languages, these are present in the shipped C#/VB.NET language defs)


        private void _editControl_Trigger(object sender, ActiproSoftware.SyntaxEditor.TriggerEventArgs e)
        {
            IntelliPromptMemberList memberList = null;
            switch(e.Trigger.Key)
            {
                case "TDLStatementListTrigger":
                    memberList = _editControl.IntelliPrompt.MemberList;

                    if((!_intellipromptInitialized)&&(_tdlIntellisenseData!=null))
                    {
                        // initialize the intelliprompt memberlist
                        memberList.ImageList = _imageList;
                        int imageIndex = 17;

                        memberList.Clear();
                        foreach(TdlIntellisenseStatementDefinition statement in _tdlIntellisenseData.Values)
                        {
                            memberList.Add(new IntelliPromptMemberListItem(
                                statement.Tokens, imageIndex, statement.Description, statement.Tokens, statement.Suffix));
                        }

                        _intellipromptInitialized = true;
                    }

                    // Show the list
                    if (memberList.Count > 0)
                    {
                        memberList.Show();
                    }
                    break;
                case "MemberListTriggerLogic":
                    memberList = CreateLptMemberList(true);
                    if(memberList!=null && (memberList.Count > 0))
                    {
                        memberList.Show();
                    }
                    break;
                case "MemberListTriggerOutput":
                    // create intelliprompt list using normal referenced assemblies and using statements.
                    memberList = CreateLptMemberList(false);
                    if(memberList!=null && (memberList.Count > 0))
                    {
                        memberList.Show();
                    }
                    break;

            }
        }

And then the beef:


        /// <summary>
        /// Creates the member list to display when a memberlisttrigger is fired.
        /// </summary>
        /// <param name="logicLanguage">flag to signal the list builder to take into account member variables available in the .lpt templates</param>
        /// <returns>null if no name was found, otherwise a filled in memberlist</returns>
        private IntelliPromptMemberList CreateLptMemberList(bool logicLanguage)
        {
            // Construct full name of item to see if reflection can be used... iterate backwards through the token stream
            TokenStream stream = _editControl.Document.GetTokenStream(_editControl.Document.Tokens.IndexOf(
                _editControl.SelectedView.Selection.EndOffset - 1));
            string fullName = String.Empty;
            int periods = 0;
            while (stream.Position > 0) 
            {
                Token token = stream.ReadReverse();
                switch (token.Key) 
                {
                    case "IdentifierToken":
                    case "NativeTypeToken":
                        fullName = token.Text + fullName;
                        break;
                    case "PunctuationToken":
                        if ((token.Length == 1) && (token.Text == ".")) 
                        {
                            fullName = token.Text + fullName;
                            periods++;
                        }
                        else
                        {
                            stream.Position = 0;
                        }
                        break;
                    default:
                        stream.Position = 0;
                        break;
                }
            }

            if(fullName.Length<=0)
            {
                // nothing found, end here.
                return null;
            }

            string[] fullNameInParts = fullName.Split('.');

            IntelliPromptTypeMemberFlags instanceType = IntelliPromptTypeMemberFlags.Static;
            switch(fullNameInParts[0]) 
            {
                case "bool":
                case "Boolean":
                    fullNameInParts[0] = "System.Boolean";
                    break;
                case "byte":
                case "Byte":
                    fullNameInParts[0] = "System.Byte";
                    break;
                case "char":
                case "Char":
                    fullNameInParts[0] = "System.Char";
                    break;
                case "decimal":
                case "Decimal":
                    fullNameInParts[0] = "System.Decimal";
                    break;
                case "double":
                case "Double":
                    fullNameInParts[0] = "System.Double";
                    break;
                case "short":
                case "Short":
                    fullNameInParts[0] = "System.Int16";
                    break;
                case "int":
                case "Integer":
                    fullNameInParts[0] = "System.Int32";
                    break;
                case "long":
                case "Long":
                    fullNameInParts[0] = "System.Int64";
                    break;
                case "object":
                case "Object":
                    fullNameInParts[0] = "System.Object";
                    break;
                case "sbyte":
                    fullNameInParts[0] = "System.SByte";
                    break;
                case "float":
                case "Single":
                    fullNameInParts[0] = "System.Single";
                    break;
                case "string":
                case "String":
                case "\"\"":
                    fullNameInParts[0] = "System.String";
                    break;
                case "ushort":
                    fullNameInParts[0] = "System.UInt16";
                    break;
                case "uint":
                    fullNameInParts[0] = "System.UInt32";
                    break;
                case "ulong":
                    fullNameInParts[0] = "System.UInt64";
                    break;
                case "void":
                    fullNameInParts[0] = "System.Void";
                    break;
                case "_executingGenerator":
                    if(logicLanguage)
                    {
                        fullNameInParts[0] = "SD.LLBLGen.Pro.GeneratorCore.Generator";
                        instanceType = IntelliPromptTypeMemberFlags.Instance;
                    }
                    break;
            }

            if(periods==0)
            {
                fullName = fullNameInParts[0];
            }

            IntelliPromptMemberList memberList = _editControl.IntelliPrompt.MemberList;
            memberList.ImageList = SyntaxEditor.ReflectionImageList;
            memberList.Clear();

            // Find a type that matches the full name
            bool typeFound = false;
            if(TextEditorMDIChild.CachedTypesInAssemblies.ContainsKey(fullName))
            {
                Type type = (Type)TextEditorMDIChild.CachedTypesInAssemblies[fullName];
                memberList.AddReflectionForTypeMembers(type, IntelliPromptTypeMemberFlags.AllMemberTypes | 
                    IntelliPromptTypeMemberFlags.Public | instanceType);
                typeFound = true;
            }

            if(!typeFound) 
            {
                // fullName was not found as a type. It can be we traversed a method or property and therefore we have to traverse that
                // list as well to find out the real type of the last name before the '.'. We do that using the fullNameInParts array, starting at the 
                // beginnning.
                Type typeLastPartInName = FindType(fullNameInParts);
                if (typeLastPartInName != null) 
                {
                    memberList.AddReflectionForTypeMembers(typeLastPartInName, IntelliPromptTypeMemberFlags.AllMemberTypes | 
                        IntelliPromptTypeMemberFlags.Public | instanceType);
                    typeFound = true;
                }
            }

            if(!typeFound)
            {
                // still not found. Simply display a list of all types seen in all namespaces.
                System.Collections.Specialized.StringCollection namespaceNames = new System.Collections.Specialized.StringCollection();
                namespaceNames.Add(fullName);
                if(logicLanguage)
                {
                    // add namespaces inside
                    namespaceNames.Add("SD.LLBLGen.Pro.ApplicationCore.Entities");
                    namespaceNames.Add("SD.LLBLGen.Pro.ApplicationCore.TypedLists");
                    namespaceNames.Add("SD.LLBLGen.Pro.ApplicationCore.StoredProcedures");
                    namespaceNames.Add("SD.LLBLGen.Pro.ApplicationCore.TypedViews");
                }

                // Create the array of flags for each Assembly... this generic example will assume we only
                //   want namespaces and types that are public
                Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
                IntelliPromptNamespaceAndTypeFlags[] flags = new IntelliPromptNamespaceAndTypeFlags[assemblies.Length];
                for (int index = 0; index < flags.Length; index++)
                {
                    flags[index] = IntelliPromptNamespaceAndTypeFlags.NamespacesAndTypes | IntelliPromptNamespaceAndTypeFlags.Public;
                }

                // Use the reflection helper method
                memberList.AddReflectionForAssemblyNamespacesAndTypes(assemblies, flags, namespaceNames);

                // Loop through the items that were created and add some descriptions
                foreach (IntelliPromptMemberListItem item in memberList) 
                {
                    if (item.ImageIndex == (int)ActiproSoftware.Products.SyntaxEditor.IconResource.Namespace)
                        item.Description = String.Format("namespace <b>{0}</b>", item.Tag.ToString());
                    else if (item.Tag is Type) 
                    {
                        Type type = (Type)item.Tag;
                        if (type.IsEnum)
                            item.Description = String.Format("enum <b>{0}</b>", type.FullName);
                        else if (type.IsInterface)
                            item.Description = String.Format("interface <b>{0}</b>", type.FullName);
                        else if (type.IsValueType)
                            item.Description = String.Format("struct <b>{0}</b>", type.FullName);
                        else if (type.IsSubclassOf(typeof(Delegate)))
                            item.Description = String.Format("delegate <b>{0}</b>", type.FullName);
                        else
                            item.Description = String.Format("class <b>{0}</b>", type.FullName);
                    }
                }
            }

            return memberList;
        }


        /// <summary>
        /// Finds the type of the last part in the fullnameinparts array, using the cached types
        /// </summary>
        /// <param name="fullNameInParts">Full name in parts.</param>
        /// <returns>null if not found, otherwise the Type object of the type of the last part in the name</returns>
        private Type FindType(string[] fullNameInParts)
        {
            Type currentType = null;
            foreach(string nameSnippet in fullNameInParts)
            {
                Type foundType = null;
                if(currentType==null)
                {
                    if(TextEditorMDIChild.CachedTypesInAssemblies.ContainsKey(nameSnippet))
                    {
                        foundType = (Type)TextEditorMDIChild.CachedTypesInAssemblies[nameSnippet];
                    }
                    else
                    {
                        // check if there is a cached type with part of this name 
                        if(TextEditorMDIChild.PartialTypeNameToCachedTypes.ContainsKey(nameSnippet))
                        {
                            ArrayList types = (ArrayList)TextEditorMDIChild.PartialTypeNameToCachedTypes[nameSnippet];
                            foundType = (Type)types[0];
                        }
                    }
                    if(foundType!=null)
                    {
                        currentType = foundType;
                        continue;
                    }
                }
                else
                {
                    // get the type of the member.
                    MemberInfo[] membersWithName = currentType.GetMember(nameSnippet);
                    if(membersWithName.Length>0)
                    {
                        // found. Use first, as there is just 1 or a list of overloads, which all return the same type
                        MemberTypes memberKind = membersWithName[0].MemberType;
                        switch(memberKind)
                        {
                            case MemberTypes.Field:
                                FieldInfo memberInfoAsFieldInfo = (FieldInfo)membersWithName[0];
                                foundType = memberInfoAsFieldInfo.FieldType;
                                break;
                            case MemberTypes.Method:
                                MethodInfo memberInfoAsMethodInfo = membersWithName[0] as MethodInfo;
                                if(memberInfoAsMethodInfo!=null)
                                {
                                    foundType = memberInfoAsMethodInfo.ReturnType;
                                }
                                break;
                            case MemberTypes.Property:
                                PropertyInfo memberInfoAsPropertyInfo = (PropertyInfo)membersWithName[0];
                                foundType = memberInfoAsPropertyInfo.PropertyType;
                                break;
                        }

                        if(foundType!=null)
                        {
                            currentType = foundType;
                        }
                    }
                }

                if(foundType==null)
                {
                    // not found, can't continue
                    return null;
                }
            }

            return currentType;
        }

Frans Bouma | Lead developer LLBLGen Pro
billb
User
Posts: 50
Joined: 09-Jul-2004
# Posted on: 08-Jan-2005 04:32:39   

I can't tell you how much I appreciate this code. You certainly went beyond the call of duty and I thank you.

Based on the documentation and the code you gave me, it looks like this editor doesn't (easily) support member functions, only types and namespaces?

So far, the control seems pretty nice.

cmartinbot
User
Posts: 147
Joined: 08-Jan-2004
# Posted on: 08-Jan-2005 10:53:31   

I heart Otis!!! simple_smile

Otis for president.

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 08-Jan-2005 12:16:14   

billb wrote:

I can't tell you how much I appreciate this code. You certainly went beyond the call of duty and I thank you.

No problem simple_smile It took me a couple of hours to figure it out, and if I can save another these hours why not? simple_smile

Based on the documentation and the code you gave me, it looks like this editor doesn't (easily) support member functions, only types and namespaces?

Well, the problem is this: say you have this code: SomeType foo = CreateFoo(); string bar = foo.CreateName();

The intellisense on 'foo' is impossible to do unless you know that 'foo' is of type SomeType. This requires 2 things: a symboltable created by the C# compiler (or vb.net if you're writing vb.net) and a notion of the scope you're currently in and thus the defined symbols for that scope. You thus need background compilation. VS.NET does this per line. If you leave a line, it compiles the line for types. It also knows in which scope you are based on the caret's position in the file ( a line is always in just 1 scope, which can be nested into another scope of course).

To get this implemented in your own code, it's pretty difficult. You need to use teh CodeDom a lot to keep track of everything. This can be simply too slow.

@cmartinbot: smile

Frans Bouma | Lead developer LLBLGen Pro