zworld-em/Plugins/UnLua/Source/UnLuaEditor/Private/UnLuaIntelliSense.cpp

560 lines
20 KiB
C++
Raw Permalink Normal View History

2025-05-11 22:07:21 +08:00
// Tencent is pleased to support the open source community by making UnLua available.
//
// Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and limitations under the License.
#include "UnLuaIntelliSense.h"
#include "Binding.h"
#include "ObjectEditorUtils.h"
#include "UnLuaInterface.h"
namespace UnLua
{
namespace IntelliSense
{
static const FName NAME_ToolTip(TEXT("ToolTip")); // key of ToolTip meta data
static const FName NAME_LatentInfo = TEXT("LatentInfo"); // tag of latent function
static FString LuaKeywords[] = {TEXT("local"), TEXT("function"), TEXT("end")};
static FString GetCommentBlock(const UField* Field)
{
const FString ToolTip = Field->GetMetaData(NAME_ToolTip);
if (ToolTip.IsEmpty())
return "";
return "---" + EscapeComments(ToolTip, false) + "\r\n";
}
FString Get(const UBlueprint* Blueprint)
{
return Get(Blueprint->GeneratedClass);
}
FString Get(const UField* Field)
{
const UStruct* Struct = Cast<UStruct>(Field);
if (Struct)
return Get(Struct);
const UEnum* Enum = Cast<UEnum>(Field);
if (Enum)
return Get(Enum);
return "";
}
FString Get(const UEnum* Enum)
{
FString Ret = GetCommentBlock(Enum);
FString TypeName = GetTypeName(Enum);
Ret += FString::Printf(TEXT("---@class %s"), *TypeName);
// fields
const int32 Num = Enum->NumEnums();
for (int32 i = 0; i < Num; ++i)
{
Ret += FString::Printf(TEXT("\r\n---@field %s %s %s"), TEXT("public"), *Enum->GetNameStringByIndex(i), TEXT("integer"));
}
// declaration
Ret += FString::Printf(TEXT("\r\nlocal %s = {}\r\n"), *EscapeSymbolName(TypeName));
return Ret;
}
FString Get(const UStruct* Struct)
{
const UClass* Class = Cast<UClass>(Struct);
if (Class)
return Get(Class);
const UScriptStruct* ScriptStruct = Cast<UScriptStruct>(Struct);
if (ScriptStruct)
return Get(ScriptStruct);
const UFunction* Function = Cast<UFunction>(Struct);
if (Function)
return Get(Function);
return "";
}
FString Get(const UScriptStruct* ScriptStruct)
{
FString Ret = GetCommentBlock(ScriptStruct);
FString TypeName = GetTypeName(ScriptStruct);
Ret += "---@class " + TypeName;
UStruct* SuperStruct = ScriptStruct->GetSuperStruct();
if (SuperStruct)
Ret += " : " + GetTypeName(SuperStruct);
Ret += "\r\n";
// fields
for (TFieldIterator<FProperty> It(ScriptStruct, EFieldIteratorFlags::ExcludeSuper, EFieldIteratorFlags::ExcludeDeprecated); It; ++It)
{
const FProperty* Property = *It;
Ret += Get(Property) += "\r\n";
}
// declaration
Ret += FString::Printf(TEXT("local %s = {}\r\n"), *EscapeSymbolName(TypeName));
// exported functions
const auto Exported = GetExportedReflectedClasses().Find(TypeName);
if (Exported)
{
TArray<IExportedFunction*> ExportedFunctions;
(*Exported)->GetFunctions(ExportedFunctions);
for (const auto Function : ExportedFunctions)
Function->GenerateIntelliSense(Ret);
}
return Ret;
}
FString Get(const UClass* Class)
{
FString Ret = GetCommentBlock(Class);
const FString TypeName = GetTypeName(Class);
Ret += "---@class " + TypeName;
UStruct* SuperStruct = Class->GetSuperStruct();
if (SuperStruct)
Ret += " : " + GetTypeName(SuperStruct);
Ret += "\r\n";
// fields
for (TFieldIterator<FProperty> It(Class, EFieldIteratorFlags::ExcludeSuper, EFieldIteratorFlags::ExcludeDeprecated); It; ++It)
{
const FProperty* Property = *It;
Ret += Get(Property) += "\r\n";
}
// declaration
const auto EscapedClassName = EscapeSymbolName(TypeName);
Ret += FString::Printf(TEXT("local %s = {}\r\n\r\n"), *EscapedClassName);
TArray<FString> GenFunctionNames;
// functions
for (TFieldIterator<UFunction> FunctionIt(Class, EFieldIteratorFlags::ExcludeSuper, EFieldIteratorFlags::ExcludeDeprecated, EFieldIteratorFlags::ExcludeInterfaces); FunctionIt; ++FunctionIt)
{
const UFunction* Function = *FunctionIt;
if (!IsValid(Function))
continue;
if (FObjectEditorUtils::IsFunctionHiddenFromClass(Function, Class))
continue;
Ret += Get(Function) + "\r\n";
GenFunctionNames.Add(Function->GetName());
}
// interface functions
for (TFieldIterator<UFunction> FunctionIt(Class, EFieldIteratorFlags::IncludeSuper, EFieldIteratorFlags::ExcludeDeprecated, EFieldIteratorFlags::IncludeInterfaces); FunctionIt; ++FunctionIt)
{
const UFunction* Function = *FunctionIt;
if (!Function->GetOwnerClass()->IsChildOf(UInterface::StaticClass()))
continue;
if (!IsValid(Function))
continue;
if (FObjectEditorUtils::IsFunctionHiddenFromClass(Function, Class))
continue;
if (GenFunctionNames.Contains(Function->GetName()))
continue;
Ret += Get(Function, EscapedClassName) + "\r\n";
}
// exported functions
const auto Exported = GetExportedReflectedClasses().Find(TypeName);
if (Exported)
{
TArray<IExportedFunction*> ExportedFunctions;
(*Exported)->GetFunctions(ExportedFunctions);
for (const auto Function : ExportedFunctions)
Function->GenerateIntelliSense(Ret);
}
return Ret;
}
FString Get(const UFunction* Function, FString ForceClassName)
{
FString Ret = GetCommentBlock(Function);
FString Properties;
for (TFieldIterator<FProperty> It(Function); It && (It->PropertyFlags & CPF_Parm); ++It)
{
const FProperty* Property = *It;
if (Property->GetFName() == NAME_LatentInfo)
continue;
FString TypeName = EscapeSymbolName(GetTypeName(Property));
const FString& PropertyComment = Property->GetMetaData(NAME_ToolTip);
FString ExtraDesc;
if (Property->HasAnyPropertyFlags(CPF_ReturnParm))
{
Ret += FString::Printf(TEXT("---@return %s"), *TypeName); // return parameter
}
else
{
FString PropertyName = EscapeSymbolName(*Property->GetName());
if (Properties.IsEmpty())
Properties = PropertyName;
else
Properties += FString::Printf(TEXT(", %s"), *PropertyName);
FName KeyName = FName(*FString::Printf(TEXT("CPP_Default_%s"), *Property->GetName()));
const FString& Value = Function->GetMetaData(KeyName);
if (!Value.IsEmpty())
{
ExtraDesc = TEXT("[opt]"); // default parameter
}
else if (Property->HasAnyPropertyFlags(CPF_OutParm) && !Property->HasAnyPropertyFlags(CPF_ConstParm))
{
ExtraDesc = TEXT("[out]"); // non-const reference
}
Ret += FString::Printf(TEXT("---@param %s %s"), *PropertyName, *TypeName);
}
if (ExtraDesc.Len() > 0 || PropertyComment.Len() > 0)
{
Ret += TEXT(" @");
if (ExtraDesc.Len() > 0)
Ret += FString::Printf(TEXT("%s "), *ExtraDesc);
}
Ret += TEXT("\r\n");
}
const auto ClassName = ForceClassName.IsEmpty() ? EscapeSymbolName(GetTypeName(Function->GetOwnerClass())) : ForceClassName;
const auto FunctionName = Function->GetName();
const auto bIsStatic = Function->HasAnyFunctionFlags(FUNC_Static);
if (IsValidFunctionName(FunctionName))
{
Ret += FString::Printf(TEXT("function %s%s%s(%s) end\r\n"), *ClassName, bIsStatic ? TEXT(".") : TEXT(":"), *FunctionName, *Properties);
}
else
{
const auto Self = bIsStatic ? TEXT("") : TEXT("self");
const auto Comma = bIsStatic || Properties.IsEmpty() ? TEXT("") : TEXT(", ");
Ret += FString::Printf(TEXT("%s[\"%s\"] = function(%s%s%s) end\r\n"), *ClassName, *FunctionName, Self, Comma, *Properties);
}
return Ret;
}
FString Get(const FProperty* Property)
{
FString Ret;
const UStruct* Struct = Property->GetOwnerStruct();
// access level
FString AccessLevel;
if (Property->HasAnyPropertyFlags(CPF_NativeAccessSpecifierPublic))
AccessLevel = TEXT("public");
else if (Property->HasAllPropertyFlags(CPF_NativeAccessSpecifierProtected))
AccessLevel = TEXT("protected");
else if (Property->HasAllPropertyFlags(CPF_NativeAccessSpecifierPrivate))
AccessLevel = TEXT("private");
else
AccessLevel = Struct->IsNative() ? "private" : "public";
FString TypeName = IntelliSense::GetTypeName(Property);
Ret += FString::Printf(TEXT("---@field %s %s %s"), *AccessLevel, *Property->GetName(), *TypeName);
// comment
const FString& ToolTip = Property->GetMetaData(NAME_ToolTip);
if (!ToolTip.IsEmpty())
Ret += " @" + EscapeComments(ToolTip, true);
return Ret;
}
FString GetUE(const TArray<const UField*> AllTypes)
{
FString Content = "UE = {";
for (const auto Type : AllTypes)
{
if (!Type->IsNative())
continue;
const auto Name = GetTypeName(Type);
Content += FString::Printf(TEXT("\r\n ---@type %s\r\n"), *Name);
Content += FString::Printf(TEXT(" %s = nil,\r\n"), *Name);
}
Content += "}\r\n";
return Content;
}
FString GetTypeName(const UObject* Field)
{
if (!Field)
return "";
if (!Field->IsNative() && Field->GetName().EndsWith("_C"))
return Field->GetName();
const UStruct* Struct = Cast<UStruct>(Field);
if (Struct)
return Struct->GetPrefixCPP() + Struct->GetName();
return Field->GetName();
}
FString GetTypeName(const FProperty* Property)
{
if (!Property)
return "Unknown";
if (CastField<FByteProperty>(Property))
return "integer";
if (CastField<FInt8Property>(Property))
return "integer";
if (CastField<FInt16Property>(Property))
return "integer";
if (CastField<FIntProperty>(Property))
return "integer";
if (CastField<FInt64Property>(Property))
return "integer";
if (CastField<FUInt16Property>(Property))
return "integer";
if (CastField<FUInt32Property>(Property))
return "integer";
if (CastField<FUInt64Property>(Property))
return "integer";
if (CastField<FFloatProperty>(Property))
return "number";
if (CastField<FDoubleProperty>(Property))
return "number";
if (CastField<FEnumProperty>(Property))
return ((FEnumProperty*)Property)->GetEnum()->GetName();
if (CastField<FBoolProperty>(Property))
return TEXT("boolean");
if (CastField<FClassProperty>(Property))
{
const UClass* Class = ((FClassProperty*)Property)->MetaClass;
return FString::Printf(TEXT("TSubclassOf<%s%s>"), Class->GetPrefixCPP(), *Class->GetName());
}
if (CastField<FSoftObjectProperty>(Property))
{
if (((FSoftObjectProperty*)Property)->PropertyClass->IsChildOf(UClass::StaticClass()))
{
const UClass* Class = ((FSoftClassProperty*)Property)->MetaClass;
return FString::Printf(TEXT("TSoftClassPtr<%s%s>"), Class->GetPrefixCPP(), *Class->GetName());
}
const UClass* Class = ((FSoftObjectProperty*)Property)->PropertyClass;
return FString::Printf(TEXT("TSoftObjectPtr<%s%s>"), Class->GetPrefixCPP(), *Class->GetName());
}
if (CastField<FObjectProperty>(Property))
{
const UClass* Class = ((FObjectProperty*)Property)->PropertyClass;
if (Cast<UBlueprintGeneratedClass>(Class))
{
return FString::Printf(TEXT("%s"), *Class->GetName());
}
return FString::Printf(TEXT("%s%s"), Class->GetPrefixCPP(), *Class->GetName());
}
if (CastField<FWeakObjectProperty>(Property))
{
const UClass* Class = ((FWeakObjectProperty*)Property)->PropertyClass;
return FString::Printf(TEXT("TWeakObjectPtr<%s%s>"), Class->GetPrefixCPP(), *Class->GetName());
}
if (CastField<FLazyObjectProperty>(Property))
{
const UClass* Class = ((FLazyObjectProperty*)Property)->PropertyClass;
return FString::Printf(TEXT("TLazyObjectPtr<%s%s>"), Class->GetPrefixCPP(), *Class->GetName());
}
if (CastField<FInterfaceProperty>(Property))
{
const UClass* Class = ((FInterfaceProperty*)Property)->InterfaceClass;
return FString::Printf(TEXT("TScriptInterface<%s%s>"), Class->GetPrefixCPP(), *Class->GetName());
}
if (CastField<FNameProperty>(Property))
return "string";
if (CastField<FStrProperty>(Property))
return "string";
if (CastField<FTextProperty>(Property))
return "string";
if (CastField<FArrayProperty>(Property))
{
const FProperty* Inner = ((FArrayProperty*)Property)->Inner;
return FString::Printf(TEXT("TArray<%s>"), *GetTypeName(Inner));
}
if (CastField<FMapProperty>(Property))
{
const FProperty* KeyProp = ((FMapProperty*)Property)->KeyProp;
const FProperty* ValueProp = ((FMapProperty*)Property)->ValueProp;
return FString::Printf(TEXT("TMap<%s, %s>"), *GetTypeName(KeyProp), *GetTypeName(ValueProp));
}
if (CastField<FSetProperty>(Property))
{
const FProperty* ElementProp = ((FSetProperty*)Property)->ElementProp;
return FString::Printf(TEXT("TSet<%s>"), *GetTypeName(ElementProp));
}
if (CastField<FStructProperty>(Property))
return ((FStructProperty*)Property)->Struct->GetStructCPPName();
if (CastField<FDelegateProperty>(Property))
return "Delegate";
if (CastField<FMulticastDelegateProperty>(Property))
return "MulticastDelegate";
return "Unknown";
}
FString EscapeComments(const FString Comments, const bool bSingleLine)
{
if (Comments.IsEmpty())
return Comments;
auto Filter = [](const FString Prefix, const FString Line)
{
if (Line.StartsWith("@"))
return FString();
return Prefix + Line;
};
TArray<FString> Lines;
Comments.Replace(TEXT("@"), TEXT("@@")).ParseIntoArray(Lines, TEXT("\n"));
FString Ret = Filter("", Lines[0]);
if (bSingleLine)
{
for (int i = 1; i < Lines.Num(); i++)
Ret += Filter(" ", Lines[i]);
}
else
{
for (int i = 1; i < Lines.Num(); i++)
Ret += Filter("\r\n---", Lines[i]);
}
return Ret;
}
FString EscapeSymbolName(const FString InName)
{
FString Name = InName;
// 和Lua关键字重名就加前缀
for (const auto& Keyword : LuaKeywords)
{
if (Name.Equals(Keyword, ESearchCase::CaseSensitive))
{
Name = TEXT("_") + Name;
break;
}
}
// 替换掉除中文外的特殊字符
for (int32 Index = 0; Index < Name.Len(); Index++)
{
const auto Char = Name[Index];
if ((Char < '0' || Char > '9')
&& (Char < 'a' || Char > 'z')
&& (Char < 'A' || Char > 'Z')
&& Char != '_'
&& Char < 0x100)
{
Name[Index] = TCHAR('_');
}
}
// 数字开头就加前缀
const auto PrefixChar = Name[0];
if (PrefixChar >= '0' && PrefixChar <= '9')
{
Name = TEXT("_") + Name;
}
return Name;
}
bool IsValid(const UFunction* Function)
{
if (!Function)
return false;
if (Function->HasAnyFunctionFlags(FUNC_UbergraphFunction))
return false;
// 这个会导致USubsystemBlueprintLibrary.GetWorldSubsystem之类的被过滤掉
// if (!UEdGraphSchema_K2::CanUserKismetCallFunction(Function))
// return false;
const FString Name = Function->GetName();
if (Name.IsEmpty())
return false;
// 避免运行时生成智能提示,把覆写的函数也生成了
if (Name.EndsWith("__Overridden"))
return false;
return true;
}
bool IsValidFunctionName(const FString Name)
{
if (Name.IsEmpty())
return false;
// 不能有Lua关键字
for (const auto& Keyword : LuaKeywords)
{
if (Name.Equals(Keyword, ESearchCase::CaseSensitive))
return false;
}
// 不能有中文和特殊字符
for (const auto& Char : Name)
{
if ((Char < '0' || Char > '9')
&& (Char < 'a' || Char > 'z')
&& (Char < 'A' || Char > 'Z')
&& Char != '_')
return false;
}
// 不能以数字开头
const auto& PrefixChar = Name[0];
if (PrefixChar >= '0' && PrefixChar <= '9')
return false;
return true;
}
}
}