Change syntax back to normal

This commit is contained in:
2026-03-19 10:16:44 -04:00
parent cc3d03541c
commit d6cc090aca
9 changed files with 85 additions and 60 deletions

View File

@@ -17,7 +17,7 @@ public:
UPROPERTY(meta=(Description="Path to a graph node (function entry, function result, custom event, or tunnel)")) UPROPERTY(meta=(Description="Path to a graph node (function entry, function result, custom event, or tunnel)"))
FString Node; FString Node;
UPROPERTY(meta=(Description="Args e.g. 'int◦x│floaty'")) UPROPERTY(meta=(Description="Args e.g. 'int x,float y'"))
FString Args; FString Args;
virtual FString GetDescription() const override virtual FString GetDescription() const override

View File

@@ -22,10 +22,10 @@ public:
"\n PATHS:" "\n PATHS:"
"\n" "\n"
"\n Most commands require you to specify a path. A path starts" "\n Most commands require you to specify a path. A path starts"
"\n with an asset name, followed by steps separated by " "\n with an asset name, followed by steps separated by ,"
"\n that navigate into the asset. Example:" "\n that navigate into the asset. Example:"
"\n" "\n"
"\n /Game/Widgets/WB_Hotkeysgraph:EventGraphnode:Self03pin:Result" "\n /Game/Widgets/WB_Hotkeys,graph:EventGraph,node:Self03,pin:Result"
"\n" "\n"
"\n The navigation steps supported are:" "\n The navigation steps supported are:"
"\n" "\n"
@@ -38,7 +38,7 @@ public:
"\n Steps do not always require a parameter. For example, materials" "\n Steps do not always require a parameter. For example, materials"
"\n only have one graph, so you can just say:" "\n only have one graph, so you can just say:"
"\n" "\n"
"\n /Game/Materials/MyMaterialgraph" "\n /Game/Materials/MyMaterial,graph"
"\n" "\n"
"\n TYPES:" "\n TYPES:"
"\n" "\n"
@@ -53,15 +53,15 @@ public:
"\n Notice that it's 'actor', not 'AActor'." "\n Notice that it's 'actor', not 'AActor'."
"\n You can use the following notations for complex types:" "\n You can use the following notations for complex types:"
"\n" "\n"
"\n Softabp_manny, Classpawn, SoftClasspawn" "\n Soft<abp_manny>, Class<pawn>, SoftClass<pawn>"
"\n Arrayint, Setstring, Mapintstring" "\n Array<int>, Set<string>, Map<int,string>"
"\n" "\n"
"\n FUNCTION ARGUMENTS AND RETURN VALUES:" "\n FUNCTION ARGUMENTS AND RETURN VALUES:"
"\n" "\n"
"\n Function argument lists are expressed as -separated" "\n Function argument lists are expressed as comma-separated"
"\n lists of typename pairs:" "\n lists of type-name pairs:"
"\n" "\n"
"\n double◦D│PlayerController◦P│Arrayint▸◦A" "\n double D,PlayerController P,Array<int> A"
"\n" "\n"
"\n To change the arguments or return values of a function, edit the" "\n To change the arguments or return values of a function, edit the"
"\n entry or exit node of the graph using GraphNode_SetArgs." "\n entry or exit node of the graph using GraphNode_SetArgs."
@@ -70,13 +70,11 @@ public:
"\n before you can set return values. Custom event nodes also have" "\n before you can set return values. Custom event nodes also have"
"\n editable arguments." "\n editable arguments."
"\n" "\n"
"\n ABOUT UNICODE AND WHITESPACE:" "\n ABOUT WHITESPACE:"
"\n" "\n"
"\n We use unicode geometric shapes as delimiters in" "\n Do not put excess whitespace into paths, typenames, or"
"\n some places, such as in typenames, function arguments, and" "\n function prototypes, only use whitespace where it is required"
"\n paths (see above). This is intentional, you really" "\n by the syntax."
"\n do have to use those delimiters. Do not put excess"
"\n whitespace into paths or typenames."
"\n" "\n"
"\n MATERIAL EDITING:" "\n MATERIAL EDITING:"
"\n" "\n"

View File

@@ -77,7 +77,7 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
if (bError) return *this; if (bError) return *this;
TArray<FString> Segments; TArray<FString> Segments;
Path.ParseIntoArray(Segments, TEXT("")); Path.ParseIntoArray(Segments, TEXT(","));
if (Segments.Num() == 0) if (Segments.Num() == 0)
{ {
UWingServer::Print(TEXT("ERROR: Empty path\n")); UWingServer::Print(TEXT("ERROR: Empty path\n"));
@@ -164,7 +164,7 @@ WingFetcher& WingFetcher::Graph(const FString& Value)
{ {
if (bError) return *this; if (bError) return *this;
// Material with blank graph name → navigate to the material graph. // Material with blank graph name
if (UMaterial* Mat = ::Cast<UMaterial>(Obj)) if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
{ {
if (!Value.IsEmpty()) if (!Value.IsEmpty())

View File

@@ -3,6 +3,7 @@
#include "K2Node_FunctionResult.h" #include "K2Node_FunctionResult.h"
#include "K2Node_Tunnel.h" #include "K2Node_Tunnel.h"
#include "WingTypes.h" #include "WingTypes.h"
#include "WingUtils.h"
#include "WingServer.h" #include "WingServer.h"
bool WingFunctionArgs::HasArgs(UEdGraphNode* Node) bool WingFunctionArgs::HasArgs(UEdGraphNode* Node)
@@ -20,8 +21,8 @@ FString WingFunctionArgs::GetArgs(UEdGraphNode* Node)
TStringBuilder<256> SB; TStringBuilder<256> SB;
for (const TSharedPtr<FUserPinInfo>& Pin : Editable->UserDefinedPins) for (const TSharedPtr<FUserPinInfo>& Pin : Editable->UserDefinedPins)
{ {
if (SB.Len() > 0) SB << TEXT(""); if (SB.Len() > 0) SB << TEXT(",");
SB << UWingTypes::TypeToText(Pin->PinType) << TEXT("") << Pin->PinName.ToString(); SB << UWingTypes::TypeToText(Pin->PinType) << TEXT(" ") << WingUtils::FormatName(*Pin);
} }
return FString(SB); return FString(SB);
} }
@@ -42,18 +43,18 @@ bool WingFunctionArgs::ParseArgs(const FString& Args, TArray<FParsedArg>& OutArg
if (Trimmed.IsEmpty()) return true; if (Trimmed.IsEmpty()) return true;
TArray<FString> Parts; TArray<FString> Parts;
Trimmed.ParseIntoArray(Parts, TEXT("")); Trimmed.ParseIntoArray(Parts, TEXT(","));
for (const FString& Part : Parts) for (const FString& Part : Parts)
{ {
FString Token = Part.TrimStartAndEnd(); FString Token = Part.TrimStartAndEnd();
if (Token.IsEmpty()) continue; if (Token.IsEmpty()) continue;
// Split "typename" on the white bullet. // Split "type name"
FString TypeStr, NameStr; FString TypeStr, NameStr;
if (!Token.Split(TEXT(""), &TypeStr, &NameStr)) if (!Token.Split(TEXT(" "), &TypeStr, &NameStr))
{ {
UWingServer::Printf(TEXT("ERROR: Expected 'typename' but got '%s'\n"), *Token); UWingServer::Printf(TEXT("ERROR: Expected 'type name' but got '%s'\n"), *Token);
return false; return false;
} }

View File

@@ -104,13 +104,13 @@ void UWingTypes::ChooseShortName(const FAssetData &Data)
{ {
// Blueprint: the generated class is AssetName_C // Blueprint: the generated class is AssetName_C
FString ObjectPath = FString::Printf(TEXT("%s.%s_C"), *PackageName, *AssetName); FString ObjectPath = FString::Printf(TEXT("%s.%s_C"), *PackageName, *AssetName);
ChooseShortName(AssetName, ObjectPath); ChooseShortName(WingUtils::SanitizeName(AssetName), ObjectPath);
} }
else if (ClassPath == UUserDefinedStruct::StaticClass()->GetClassPathName() || else if (ClassPath == UUserDefinedStruct::StaticClass()->GetClassPathName() ||
ClassPath == UUserDefinedEnum::StaticClass()->GetClassPathName()) ClassPath == UUserDefinedEnum::StaticClass()->GetClassPathName())
{ {
FString ObjectPath = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName); FString ObjectPath = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
ChooseShortName(AssetName, ObjectPath); ChooseShortName(WingUtils::SanitizeName(AssetName), ObjectPath);
} }
} }
@@ -159,11 +159,11 @@ FString UWingTypes::TypeToTextInner(FName Category, FName SubCategory, UObject*
if (Category == UEdGraphSchema_K2::PC_Object) if (Category == UEdGraphSchema_K2::PC_Object)
return Short; return Short;
if (Category == UEdGraphSchema_K2::PC_Class) if (Category == UEdGraphSchema_K2::PC_Class)
return FString::Printf(TEXT("Class%s"), *Short); return FString::Printf(TEXT("Class<%s>"), *Short);
if (Category == UEdGraphSchema_K2::PC_SoftObject) if (Category == UEdGraphSchema_K2::PC_SoftObject)
return FString::Printf(TEXT("Soft%s"), *Short); return FString::Printf(TEXT("Soft<%s>"), *Short);
if (Category == UEdGraphSchema_K2::PC_SoftClass) if (Category == UEdGraphSchema_K2::PC_SoftClass)
return FString::Printf(TEXT("SoftClass%s"), *Short); return FString::Printf(TEXT("SoftClass<%s>"), *Short);
if (Category == UEdGraphSchema_K2::PC_Interface) if (Category == UEdGraphSchema_K2::PC_Interface)
return Short; return Short;
} }
@@ -181,9 +181,9 @@ FString UWingTypes::TypeToText(const FEdGraphPinType& PinType)
return FString(); return FString();
if (PinType.IsArray()) if (PinType.IsArray())
return FString::Printf(TEXT("Array%s"), *Inner); return FString::Printf(TEXT("Array<%s>"), *Inner);
if (PinType.IsSet()) if (PinType.IsSet())
return FString::Printf(TEXT("Set%s"), *Inner); return FString::Printf(TEXT("Set<%s>"), *Inner);
if (PinType.IsMap()) if (PinType.IsMap())
{ {
FString ValueInner = Types->TypeToTextInner( FString ValueInner = Types->TypeToTextInner(
@@ -192,7 +192,7 @@ FString UWingTypes::TypeToText(const FEdGraphPinType& PinType)
PinType.PinValueType.TerminalSubCategoryObject.Get()); PinType.PinValueType.TerminalSubCategoryObject.Get());
if (ValueInner.IsEmpty()) if (ValueInner.IsEmpty())
return FString(); return FString();
return FString::Printf(TEXT("Map%s%s"), *Inner, *ValueInner); return FString::Printf(TEXT("Map<%s,%s>"), *Inner, *ValueInner);
} }
return Inner; return Inner;
@@ -447,29 +447,29 @@ bool UWingTypes::ParsePlainIdentifier(FEdGraphPinType& OutType)
bool UWingTypes::ParseWrapped(FName Wrapper, FEdGraphPinType& OutType) bool UWingTypes::ParseWrapped(FName Wrapper, FEdGraphPinType& OutType)
{ {
Cursor++; Cursor++;
if (!ParseChar(L'')) return false; if (!ParseChar('<')) return false;
if (!ParsePlainIdentifier(OutType)) return false; if (!ParsePlainIdentifier(OutType)) return false;
if (!Cast<UClass>(OutType.PinSubCategoryObject)) if (!Cast<UClass>(OutType.PinSubCategoryObject))
{ {
Error = FString::Printf(TEXT("%s is not a Class"), *OutType.PinSubCategoryObject->GetName()); Error = FString::Printf(TEXT("%s is not a Class"), *OutType.PinSubCategoryObject->GetName());
return false; return false;
} }
if (!ParseChar(L'')) return false; if (!ParseChar('>')) return false;
OutType.PinCategory = Wrapper; OutType.PinCategory = Wrapper;
return true; return true;
} }
bool UWingTypes::ParseMaybeWrapped(FEdGraphPinType& OutType) bool UWingTypes::ParseMaybeWrapped(FEdGraphPinType& OutType)
{ {
if (TokenIs(TEXT("Soft"), L'')) if (TokenIs(TEXT("Soft"), '<'))
{ {
return ParseWrapped(UEdGraphSchema_K2::PC_SoftObject, OutType); return ParseWrapped(UEdGraphSchema_K2::PC_SoftObject, OutType);
} }
else if (TokenIs(TEXT("Class"), L'')) else if (TokenIs(TEXT("Class"), '<'))
{ {
return ParseWrapped(UEdGraphSchema_K2::PC_Class, OutType); return ParseWrapped(UEdGraphSchema_K2::PC_Class, OutType);
} }
else if (TokenIs(TEXT("SoftClass"), L'')) else if (TokenIs(TEXT("SoftClass"), '<'))
{ {
return ParseWrapped(UEdGraphSchema_K2::PC_SoftClass, OutType); return ParseWrapped(UEdGraphSchema_K2::PC_SoftClass, OutType);
} }
@@ -479,40 +479,40 @@ bool UWingTypes::ParseMaybeWrapped(FEdGraphPinType& OutType)
bool UWingTypes::ParseArrayOrSet(FEdGraphPinType& OutType) bool UWingTypes::ParseArrayOrSet(FEdGraphPinType& OutType)
{ {
Cursor++; Cursor++;
if (!ParseChar(L'')) return false; if (!ParseChar('<')) return false;
if (!ParseMaybeWrapped(OutType)) return false; if (!ParseMaybeWrapped(OutType)) return false;
if (!ParseChar(L'')) return false; if (!ParseChar('>')) return false;
return true; return true;
} }
bool UWingTypes::ParseMap(FEdGraphPinType& OutType) bool UWingTypes::ParseMap(FEdGraphPinType& OutType)
{ {
Cursor++; Cursor++;
if (!ParseChar(L'')) return false; if (!ParseChar('<')) return false;
if (!ParsePlainIdentifier(OutType)) return false; if (!ParsePlainIdentifier(OutType)) return false;
if (!ParseChar(L'')) return false; if (!ParseChar(',')) return false;
FEdGraphPinType ValueType; FEdGraphPinType ValueType;
if (!ParseMaybeWrapped(ValueType)) return false; if (!ParseMaybeWrapped(ValueType)) return false;
OutType.PinValueType.TerminalCategory = ValueType.PinCategory; OutType.PinValueType.TerminalCategory = ValueType.PinCategory;
OutType.PinValueType.TerminalSubCategory = ValueType.PinSubCategory; OutType.PinValueType.TerminalSubCategory = ValueType.PinSubCategory;
OutType.PinValueType.TerminalSubCategoryObject = ValueType.PinSubCategoryObject; OutType.PinValueType.TerminalSubCategoryObject = ValueType.PinSubCategoryObject;
if (!ParseChar(L'')) return false; if (!ParseChar('>')) return false;
return true; return true;
} }
bool UWingTypes::ParseType(FEdGraphPinType& OutType) bool UWingTypes::ParseType(FEdGraphPinType& OutType)
{ {
if (TokenIs(TEXT("Array"), L'')) if (TokenIs(TEXT("Array"), '<'))
{ {
OutType.ContainerType = EPinContainerType::Array; OutType.ContainerType = EPinContainerType::Array;
if (!ParseArrayOrSet(OutType)) return false; if (!ParseArrayOrSet(OutType)) return false;
} }
else if (TokenIs(TEXT("Set"), L'')) else if (TokenIs(TEXT("Set"), '<'))
{ {
OutType.ContainerType = EPinContainerType::Set; OutType.ContainerType = EPinContainerType::Set;
if (!ParseArrayOrSet(OutType)) return false; if (!ParseArrayOrSet(OutType)) return false;
} }
else if (TokenIs(TEXT("Map"), L'')) else if (TokenIs(TEXT("Map"), '<'))
{ {
OutType.ContainerType = EPinContainerType::Map; OutType.ContainerType = EPinContainerType::Map;
if (!ParseMap(OutType)) return false; if (!ParseMap(OutType)) return false;

View File

@@ -11,6 +11,7 @@
#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphPin.h"
#include "K2Node_EditablePinBase.h"
#include "EdGraph/EdGraphSchema.h" #include "EdGraph/EdGraphSchema.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h" #include "Kismet2/KismetEditorUtilities.h"
@@ -53,14 +54,11 @@ extern int32 TrySavePackageSEH(
// ============================================================ // ============================================================
// Name sanitization // Name sanitization
// //
// Our parsers use Unicode geometric shape delimiters that // Our parsers reserve certain punctuation marks for parsing
// are unlikely to appear in names: ◂▸ for type brackets, ◆ // types, paths, and the like. For example: Array<Int>.
// for map key◆value, ◦ for type◦name in prototypes, │ for // We therefore cannot allow those specific punctuation marks
// list/path separation. If any of these characters appear // in names. We replace them with similar-looking unicode
// in a name, we replace them with safe ASCII equivalents. // characters.
// One consequence of name sanitization is that we can't use
// Unreal's name-lookup routines — the LLM only sees
// sanitized names. So we do our own name lookups.
// ============================================================ // ============================================================
void WingUtils::SanitizeNameInPlace(FString &Name) void WingUtils::SanitizeNameInPlace(FString &Name)
@@ -70,14 +68,13 @@ void WingUtils::SanitizeNameInPlace(FString &Name)
{ {
TCHAR c = Name[Src]; TCHAR c = Name[Src];
if (c < 0x20 || c == 0x7F) continue; if (c < 0x20 || c == 0x7F) continue;
if (c == L'') c='<'; if (c == ' ') c=L'·';
if (c == L'') c='>'; if (c == '<') c=L'';
if (c == L'') c='*'; if (c == '>') c=L'';
if (c == L'') c='.'; if (c == ',') c=L'·';
if (c == L'') c = '|';
Name[Dst++] = c; Name[Dst++] = c;
} }
if (Dst == 0) Name[Dst++] = '_'; if (Dst == 0) Name[Dst++] = L'·';
Name.LeftInline(Dst); Name.LeftInline(Dst);
} }
@@ -216,6 +213,11 @@ FString WingUtils::FormatName(const FProperty *Prop)
return SanitizeName(Prop->GetName()); return SanitizeName(Prop->GetName());
} }
FString WingUtils::FormatName(const FUserPinInfo &Pin)
{
return SanitizeName(Pin.PinName);
}
// ============================================================ // ============================================================
// Formatting other things // Formatting other things
// ============================================================ // ============================================================

View File

@@ -13,10 +13,10 @@ struct WingFunctionArgs
// CustomEvent, or Tunnel). // CustomEvent, or Tunnel).
static bool HasArgs(UEdGraphNode* Node); static bool HasArgs(UEdGraphNode* Node);
// Returns the user-defined pins as a string like "int◦x│floaty". // Returns the user-defined pins as a string like "int x,float y".
static FString GetArgs(UEdGraphNode* Node); static FString GetArgs(UEdGraphNode* Node);
// Sets the user-defined pins from a string like "int◦x│floaty". // Sets the user-defined pins from a string like "int x,float y".
// Returns true on success. // Returns true on success.
static bool SetArgs(UEdGraphNode* Node, const FString& Args); static bool SetArgs(UEdGraphNode* Node, const FString& Args);

View File

@@ -30,6 +30,7 @@ class UEnum;
class USCS_Node; class USCS_Node;
struct FMemberReference; struct FMemberReference;
struct FBPVariableDescription; struct FBPVariableDescription;
struct FUserPinInfo;
// Stateless utility functions used by MCP handlers and the MCP server. // Stateless utility functions used by MCP handlers and the MCP server.
// This is effectively a namespace — all methods are static. // This is effectively a namespace — all methods are static.
class WingUtils class WingUtils
@@ -71,6 +72,7 @@ public:
static FString FormatName(const UScriptStruct *Struct); static FString FormatName(const UScriptStruct *Struct);
static FString FormatName(const UEnum *Enum); static FString FormatName(const UEnum *Enum);
static FString FormatName(const FProperty *Prop); static FString FormatName(const FProperty *Prop);
static FString FormatName(const FUserPinInfo &Pin);
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
// //

22
find_nonascii.py Normal file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3
"""Search UEWingman source files for non-ASCII characters."""
import os
import glob
source_dir = "Plugins/UEWingman/Source"
patterns = ["**/*.cpp", "**/*.h"]
found_any = False
for pattern in patterns:
for filepath in sorted(glob.glob(os.path.join(source_dir, pattern), recursive=True)):
with open(filepath, "r", encoding="utf-8") as f:
for lineno, line in enumerate(f, 1):
for col, ch in enumerate(line, 1):
if ord(ch) > 127:
if not found_any:
found_any = True
print(f"{filepath}:{lineno}:{col} U+{ord(ch):04X} {ch!r} ...{line[max(0,col-10):col+10].rstrip()}...")
if not found_any:
print("No non-ASCII characters found.")