PythonCodeCompletionProviderCommon
This class represents a base class for Python code completion providers
It partially implements the IExternalCodeCompletionProviderCore interface and
contains a collection of utility functions/properties that are common among existing code completion provider classes
using Autodesk.DesignScript.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
namespace Dynamo.PythonServices
{
internal abstract class PythonCodeCompletionProviderCommon : IExternalCodeCompletionProviderCore
{
protected enum PythonScriptType
{
SingleStatement,
Statements,
Expression
}
internal static readonly string commaDelimitedVariableNamesRegex = "(([0-9a-zA-Z_]+,?\\s?)+)";
internal static readonly string variableName = "([0-9a-zA-Z_]+(\\.[a-zA-Z_0-9]+)*)";
internal static readonly string spacesOrNone = "(\\s*)";
internal static readonly string atLeastOneSpaceRegex = "(\\s+)";
internal static readonly string dictRegex = "({.*})";
internal static readonly string basicImportRegex = "(import)";
internal static readonly string fromImportRegex = "^(from)";
internal static string arrayRegex = "(\\[.*\\])";
internal static string doubleRegex = "([-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?)";
internal static string intRegex = "([-+]?\\d+)[\\s\\n]*$";
internal const string quotesStringRegex = "[\"']([^\"']*)[\"']";
internal const string equalsRegex = "(=)";
internal static readonly Regex MATCH_LAST_NAMESPACE = new Regex("[\\w.]+$", RegexOptions.Compiled);
internal static readonly Regex MATCH_LAST_WORD = new Regex("\\w+$", RegexOptions.Compiled);
internal static readonly Regex MATCH_FIRST_QUOTED_NAME = new Regex("[\"']([^\"']*)[\"']", RegexOptions.Compiled);
internal static readonly Regex MATCH_VALID_TYPE_NAME_CHARACTERS_ONLY = new Regex("^\\w+", RegexOptions.Compiled);
internal static readonly Regex TRIPLE_QUOTE_STRINGS = new Regex(".*?\\\"{{3}}[\\s\\S]+?\\\"{{3}}", RegexOptions.Compiled);
internal static readonly Regex MATCH_IMPORT_STATEMENTS = new Regex("^import\\s+?(.+)", RegexOptions.Multiline | RegexOptions.Compiled);
internal static readonly Regex MATCH_FROM_IMPORT_STATEMENTS = new Regex("from\\s+?([\\w.]+)\\s+?import\\s+?([\\w, *]+)", RegexOptions.Multiline | RegexOptions.Compiled);
internal static readonly Regex MATCH_VARIABLE_ASSIGNMENTS = new Regex("^[ \\t]*?(\\w+(\\s*?,\\s*?\\w+)*)\\s*?=\\s*(.+)", RegexOptions.Multiline | RegexOptions.Compiled);
internal static readonly Regex STRING_VARIABLE = new Regex("^[\"']([^\"']*)[\"']$", RegexOptions.Compiled);
internal static readonly Regex DOUBLE_VARIABLE = new Regex("^-?\\d+\\.\\d+$", RegexOptions.Compiled);
internal static readonly Regex INT_VARIABLE = new Regex("^-?\\d+$", RegexOptions.Compiled);
internal static readonly Regex LIST_VARIABLE = new Regex("^\\[.*\\]$", RegexOptions.Compiled);
internal static readonly Regex DICT_VARIABLE = new Regex("^{.*}$", RegexOptions.Compiled);
internal static readonly string BAD_ASSIGNEMNT_ENDS = ",([{";
internal static readonly string inBuiltMethod = "built-in";
internal static readonly string method = "method";
internal static readonly string internalType = "Autodesk";
internal static readonly string clrReference = "clr.AddReference";
private static string[] knownAssemblies = new string[4] {
"mscorlib",
"RevitAPI",
"RevitAPIUI",
"ProtoGeometry"
};
protected List<Tuple<Regex, Type>> BasicVariableTypes;
protected HashSet<string> ClrModules { get; set; }
protected Dictionary<string, int> BadStatements { get; set; }
public Dictionary<string, Type> VariableTypes { get; set; }
public Dictionary<string, Type> ImportedTypes { get; set; }
protected abstract object GetDescriptionObject(string docCommand);
public abstract IExternalCodeCompletionData[] GetCompletionData(string code, bool expand = false);
public string GetDescription(string stub, string item, bool isInstance)
{
string result = "No description available";
if (!string.IsNullOrEmpty(item))
try {
string text = "";
text = ((!isInstance) ? (stub + "." + item + ".__doc__") : ("type(" + stub + ")." + item + ".__doc__"));
object descriptionObject = GetDescriptionObject(text);
if (string.IsNullOrEmpty((string)descriptionObject))
return result;
result = (string)descriptionObject;
return result;
} catch {
return result;
}
return result;
}
public abstract bool IsSupportedEngine(string engineName);
public abstract void Initialize(string dynamoCorePath);
protected IEnumerable<Tuple<string, string, bool, ExternalCodeCompletionType>> EnumerateMembers(Type type, string name)
{
List<Tuple<string, string, bool, ExternalCodeCompletionType>> list = new List<Tuple<string, string, bool, ExternalCodeCompletionType>>();
SortedList<string, ExternalCodeCompletionType> sortedList = new SortedList<string, ExternalCodeCompletionType>();
MethodInfo[] methods = type.GetMethods();
PropertyInfo[] properties = type.GetProperties();
FieldInfo[] fields = type.GetFields();
MethodInfo[] array = methods;
foreach (MethodInfo methodInfo in array) {
if (methodInfo.IsPublic && methodInfo.Name.IndexOf("get_") != 0 && methodInfo.Name.IndexOf("set_") != 0 && methodInfo.Name.IndexOf("add_") != 0 && methodInfo.Name.IndexOf("remove_") != 0 && methodInfo.Name.IndexOf("__") != 0 && !sortedList.ContainsKey(methodInfo.Name))
sortedList.Add(methodInfo.Name, ExternalCodeCompletionType.Method);
}
PropertyInfo[] array2 = properties;
foreach (PropertyInfo propertyInfo in array2) {
if (!sortedList.ContainsKey(propertyInfo.Name))
sortedList.Add(propertyInfo.Name, ExternalCodeCompletionType.Property);
}
FieldInfo[] array3 = fields;
foreach (FieldInfo fieldInfo in array3) {
if (!sortedList.ContainsKey(fieldInfo.Name))
sortedList.Add(fieldInfo.Name, ExternalCodeCompletionType.Field);
}
if (type.IsEnum) {
string[] enumNames = type.GetEnumNames();
foreach (string key in enumNames) {
if (!sortedList.ContainsKey(key))
sortedList.Add(key, ExternalCodeCompletionType.Field);
}
}
foreach (KeyValuePair<string, ExternalCodeCompletionType> item in sortedList) {
list.Add(Tuple.Create(item.Key, name, true, item.Value));
}
return list;
}
protected static string GetLastName(string text)
{
return MATCH_LAST_WORD.Match(text.Trim(new char[1] {
'.'
}).Trim()).Value;
}
protected static string GetLastNameSpace(string text)
{
return MATCH_LAST_NAMESPACE.Match(text.Trim(new char[1] {
'.'
}).Trim()).Value;
}
protected static string GetFirstPossibleTypeName(string line)
{
Match match = MATCH_VALID_TYPE_NAME_CHARACTERS_ONLY.Match(line);
return match.Success ? match.Value : "";
}
protected static string StripDocStrings(string code)
{
string[] value = TRIPLE_QUOTE_STRINGS.Split(code);
return string.Join("", value);
}
protected static List<string> FindClrReferences(string code)
{
List<string> list = new List<string>();
string[] array = code.Split('\n', ';');
foreach (string text in array) {
if (text.Contains(clrReference))
list.Add(text.Trim());
}
return list;
}
protected static Type TryGetTypeFromFullName(string name)
{
string[] array = knownAssemblies;
foreach (string arg in array) {
Type type = Type.GetType($"{name}""{arg}");
if (type != (Type)null)
return type;
}
return null;
}
protected abstract object EvaluateScript(string script, PythonScriptType type);
protected abstract void LogError(string msg);
protected internal abstract bool ScopeHasVariable(string name);
protected abstract Type GetCLRType(string name);
protected internal void UpdateImportedTypes(string code)
{
List<string> list = FindClrReferences(code);
foreach (string item4 in list) {
int value = 0;
BadStatements.TryGetValue(item4, out value);
if (value <= 3)
try {
string libName = MATCH_FIRST_QUOTED_NAME.Match(item4).Groups[1].Value;
if (!ClrModules.Contains(libName)) {
if (item4.Contains("AddReferenceToFileAndPath")) {
EvaluateScript(item4, PythonScriptType.SingleStatement);
ClrModules.Add(libName);
} else if (AppDomain.CurrentDomain.GetAssemblies().Any((Assembly x) => x.GetName().Name == libName)) {
EvaluateScript(item4, PythonScriptType.SingleStatement);
ClrModules.Add(libName);
}
}
} catch (Exception ex) {
LogError($"""{item4}");
LogError(ex.ToString());
BadStatements[item4] = value + 1;
}
}
List<Tuple<string, string, string>> list2 = FindAllImportStatements(code);
foreach (Tuple<string, string, string> item5 in list2) {
string item = item5.Item1;
string item2 = item5.Item2;
string item3 = item5.Item3;
string text = item3 ?? item2;
string text2 = "";
int value2 = 0;
if (!(text != "*") || (!ScopeHasVariable(text) && !ImportedTypes.ContainsKey(text)))
try {
text2 = ((item == null) ? ((item3 != null) ? $"""{item2}""{item3}" : $"""{item2}") : ((!(item2 != "*") || item3 == null) ? $"""{item}""" : $"""{item}""{item2}""{item3}"));
BadStatements.TryGetValue(text2, out value2);
if (value2 <= 3) {
EvaluateScript(text2, PythonScriptType.SingleStatement);
if (!(item2 == "*")) {
string typeName = (item == null) ? item2 : $"{item}""{item2}";
Type type = Type.GetType(typeName);
ImportedTypes.Add(text, type);
}
}
} catch (Exception) {
LogError($"""{item2}""{text2}");
BadStatements[text2] = value2 + 1;
}
}
}
internal void UpdateVariableTypes(string code)
{
VariableTypes.Clear();
VariableTypes = FindAllVariableAssignments(code);
}
protected Dictionary<string, Type> FindAllVariableAssignments(string code)
{
Dictionary<string, Type> dictionary = new Dictionary<string, Type>();
MatchCollection matchCollection = MATCH_VARIABLE_ASSIGNMENTS.Matches(code);
foreach (Match item in matchCollection) {
string text = item.Groups[1].Value.Trim();
string text2 = item.Groups[3].Value.Trim();
if (!Enumerable.Contains(BAD_ASSIGNEMNT_ENDS, text2.Last())) {
string[] array = (from x in text.Split(new char[1] {
','
})
select x.Trim()).ToArray();
string[] array2 = (from x in text2.Split(new char[1] {
','
})
select x.Trim()).ToArray();
if (array2.Length >= array.Length) {
if (array.Length == 1 && array2.Length > 1)
array2 = new string[1] {
text2
};
if (array.Length == array2.Length) {
for (int i = 0; i < array.Length; i++) {
bool flag = false;
foreach (Tuple<Regex, Type> basicVariableType in BasicVariableTypes) {
if (basicVariableType.Item1.IsMatch(array2[i])) {
dictionary[array[i]] = basicVariableType.Item2;
flag = true;
break;
}
}
if (!flag) {
string firstPossibleTypeName = GetFirstPossibleTypeName(array2[i]);
if (!string.IsNullOrEmpty(firstPossibleTypeName)) {
if (!dictionary.TryGetValue(firstPossibleTypeName, out Type value))
value = TryGetType(firstPossibleTypeName);
if (value != (Type)null)
dictionary[array[i]] = value;
}
}
}
}
}
}
}
return dictionary;
}
protected Type TryGetType(string name)
{
if (ImportedTypes.ContainsKey(name))
return ImportedTypes[name];
Type type = null;
try {
type = GetCLRType(name);
} catch (Exception ex) {
LogError($"""{name}");
LogError(ex.ToString());
}
if (type != (Type)null)
ImportedTypes[name] = type;
return type;
}
internal static Dictionary<string, string> FindAllTypeImportStatements(string code)
{
string pattern = fromImportRegex + atLeastOneSpaceRegex + variableName + atLeastOneSpaceRegex + basicImportRegex + atLeastOneSpaceRegex + "\\*$";
MatchCollection matchCollection = Regex.Matches(code, pattern, RegexOptions.Multiline);
Dictionary<string, string> dictionary = new Dictionary<string, string>();
for (int i = 0; i < matchCollection.Count; i++) {
string value = matchCollection[i].Groups[0].Value;
string key = matchCollection[i].Groups[3].Value.Trim();
if (!dictionary.ContainsKey(key))
dictionary.Add(key, value);
}
return dictionary;
}
internal static Dictionary<string, string> FindTypeSpecificImportStatements(string code)
{
string pattern = fromImportRegex + atLeastOneSpaceRegex + variableName + atLeastOneSpaceRegex + basicImportRegex + atLeastOneSpaceRegex + commaDelimitedVariableNamesRegex + "$";
MatchCollection matchCollection = Regex.Matches(code, pattern, RegexOptions.Multiline);
Dictionary<string, string> dictionary = new Dictionary<string, string>();
for (int i = 0; i < matchCollection.Count; i++) {
string text = matchCollection[i].Groups[0].Value.TrimEnd('\r', '\n');
string text2 = matchCollection[i].Groups[8].Value.Trim();
string[] array = text2.Replace(" ", "").Split(new char[1] {
','
});
string[] array2 = array;
foreach (string text3 in array2) {
if (!dictionary.ContainsKey(text3))
dictionary.Add(text3, text.Replace(text2, text3));
}
}
return dictionary;
}
internal static Dictionary<string, string> FindVariableStatementWithRegex(string code, string valueRegex)
{
string pattern = variableName + spacesOrNone + "(=)" + spacesOrNone + valueRegex;
MatchCollection matchCollection = Regex.Matches(code, pattern);
Dictionary<string, string> dictionary = new Dictionary<string, string>();
for (int i = 0; i < matchCollection.Count; i++) {
string key = matchCollection[i].Groups[1].Value.Trim();
string value = matchCollection[i].Groups[6].Value.Trim();
dictionary.Add(key, value);
}
return dictionary;
}
protected static List<Tuple<string, string, string>> FindAllImportStatements(string code)
{
List<Tuple<string, string, string>> list = new List<Tuple<string, string, string>>();
MatchCollection matchCollection = MATCH_IMPORT_STATEMENTS.Matches(code);
foreach (Match item5 in matchCollection) {
List<string> list2 = new List<string>();
if (item5.Value.EndsWith(".")) {
foreach (Group group in item5.Groups) {
string value = item5.Value;
value = value.Replace("\t", " ").Replace("\n", " ").Replace("\r", " ");
int val = value.LastIndexOf(' ');
int val2 = value.LastIndexOf('=');
string text = value.Substring(Math.Max(val, val2) + 1).Trim(new char[1] {
'.'
}).Trim(new char[1] {
'('
});
List<string> list3 = (from x in text.Trim().Split(new char[1] {
','
})
select x.Trim()).ToList();
foreach (string item6 in list3) {
list2.Add(item6);
}
}
} else
list2 = (from x in item5.Groups[1].Value.Trim().Split(new char[1] {
','
})
select x.Trim()).ToList();
foreach (string item7 in list2) {
string[] array = item7.Split(new string[1] {
" as "
}, 2, StringSplitOptions.RemoveEmptyEntries);
string item = array[0];
string item2 = (array.Length > 1) ? array[1] : null;
list.Add(new Tuple<string, string, string>(null, item, item2));
}
}
MatchCollection matchCollection2 = MATCH_FROM_IMPORT_STATEMENTS.Matches(code);
foreach (Match item8 in matchCollection2) {
string value2 = item8.Groups[1].Value;
IEnumerable<string> enumerable = from x in item8.Groups[2].Value.Trim().Split(new char[1] {
','
})
select x.Trim();
foreach (string item9 in enumerable) {
string[] array2 = item9.Split(new string[1] {
" as "
}, 2, StringSplitOptions.RemoveEmptyEntries);
string item3 = array2[0];
string item4 = (array2.Length > 1) ? array2[1] : null;
list.Add(new Tuple<string, string, string>(value2, item3, item4));
}
}
return list;
}
internal Dictionary<string, Tuple<string, int, Type>> FindAllVariables(string code)
{
Dictionary<string, Tuple<string, int, Type>> dictionary = new Dictionary<string, Tuple<string, int, Type>>();
string pattern = variableName + spacesOrNone + "(=)" + spacesOrNone + "(.*)";
MatchCollection matchCollection = Regex.Matches(code, pattern, RegexOptions.Multiline);
for (int i = 0; i < matchCollection.Count; i++) {
string key = matchCollection[i].Groups[1].Value.Trim();
string text = matchCollection[i].Groups[6].Value.Trim();
int index = matchCollection[i].Index;
string firstPossibleTypeName = GetFirstPossibleTypeName(text);
if (!string.IsNullOrEmpty(firstPossibleTypeName)) {
Type type = TryGetType(firstPossibleTypeName);
if (type != (Type)null) {
if (dictionary.ContainsKey(key)) {
if (index > dictionary[key].Item2)
dictionary[key] = new Tuple<string, int, Type>(text, index, type);
} else
dictionary.Add(key, new Tuple<string, int, Type>(text, index, type));
continue;
}
}
foreach (Tuple<Regex, Type> basicVariableType in BasicVariableTypes) {
MatchCollection matchCollection2 = Regex.Matches(text, "^" + basicVariableType.Item1?.ToString() + "$", RegexOptions.Singleline);
if (matchCollection2.Count > 0) {
if (dictionary.ContainsKey(key)) {
if (index > dictionary[key].Item2)
dictionary[key] = new Tuple<string, int, Type>(text, index, basicVariableType.Item2);
} else
dictionary.Add(key, new Tuple<string, int, Type>(text, index, basicVariableType.Item2));
break;
}
}
}
return dictionary;
}
internal static Dictionary<string, string> FindBasicImportStatements(string code)
{
string pattern = "^" + basicImportRegex + spacesOrNone + variableName;
MatchCollection matchCollection = Regex.Matches(code, pattern, RegexOptions.Multiline);
Dictionary<string, string> dictionary = new Dictionary<string, string>();
for (int i = 0; i < matchCollection.Count; i++) {
string value = matchCollection[i].Groups[0].Value;
string key = matchCollection[i].Groups[3].Value.Trim();
if (!dictionary.ContainsKey(key))
dictionary.Add(key, value);
}
return dictionary;
}
}
}