Framework for printing abridged manual sections in response to syntactic mistakes
This commit is contained in:
@@ -27,7 +27,7 @@ public:
|
||||
FString Graph;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="True to include less-significant details"))
|
||||
bool bDetails;
|
||||
bool IncludeDetails;
|
||||
|
||||
virtual FString GetDescription() const override
|
||||
{
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
|
||||
WingGraphExport Exporter(G);
|
||||
UWingServer::Print(*Exporter.GetOutput());
|
||||
if (bDetails)
|
||||
if (IncludeDetails)
|
||||
{
|
||||
UWingServer::Print(Exporter.GetDetails());
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "WingServer.h"
|
||||
#include "WingTypes.h"
|
||||
#include "WingJson.h"
|
||||
#include "WingUtils.h"
|
||||
#include "WingManual.h"
|
||||
#include "ShowCommands.generated.h"
|
||||
|
||||
UCLASS()
|
||||
@@ -26,60 +26,37 @@ public:
|
||||
return TEXT("List all available commands with their descriptions.");
|
||||
}
|
||||
|
||||
void EmitCommand(UClass* Class)
|
||||
{
|
||||
if (Verbose)
|
||||
{
|
||||
WingUtils::PrintHandlerHelp(Class);
|
||||
return;
|
||||
}
|
||||
UWingServer::Print(WingUtils::GetHandlerName(Class));
|
||||
UWingServer::Print(TEXT("("));
|
||||
bool bFirst = true;
|
||||
for (TFieldIterator<FProperty> PropIt(Class, EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||
{
|
||||
if (!bFirst) UWingServer::Print(TEXT(","));
|
||||
bFirst = false;
|
||||
if (PropIt->HasMetaData(TEXT("Optional"))) UWingServer::Print(TEXT("?"));
|
||||
UWingServer::Print(PropIt->GetName());
|
||||
}
|
||||
UWingServer::Print(TEXT(")\n"));
|
||||
}
|
||||
|
||||
void EmitCommandList(bool bHalfBaked)
|
||||
virtual void Handle() override
|
||||
{
|
||||
FString QueryLower = Query.ToLower();
|
||||
FString PrevGroup;
|
||||
|
||||
for (UClass* Class : WingUtils::CollectHandlerClasses())
|
||||
{
|
||||
bool bIsHalfBaked = Class->GetMetaData(TEXT("ModuleRelativePath")).StartsWith(TEXT("HalfBaked/"));
|
||||
if (bIsHalfBaked != bHalfBaked)
|
||||
continue;
|
||||
|
||||
FString ToolName = WingUtils::GetHandlerName(Class);
|
||||
if (!ToolName.ToLower().Contains(QueryLower))
|
||||
continue;
|
||||
|
||||
// Blank line between groups
|
||||
FString Group = WingUtils::GetHandlerGroup(Class);
|
||||
if (Group != PrevGroup)
|
||||
if (!Verbose)
|
||||
{
|
||||
if (!PrevGroup.IsEmpty())
|
||||
UWingServer::Print(TEXT("\n"));
|
||||
PrevGroup = Group;
|
||||
FString Group = WingUtils::GetHandlerGroup(Class);
|
||||
if (Group != PrevGroup)
|
||||
{
|
||||
if (!PrevGroup.IsEmpty())
|
||||
UWingServer::Print(TEXT("\n"));
|
||||
PrevGroup = Group;
|
||||
}
|
||||
}
|
||||
|
||||
EmitCommand(Class);
|
||||
if (Verbose)
|
||||
{
|
||||
WingManual::PrintHandlerHelp(Class);
|
||||
}
|
||||
else
|
||||
{
|
||||
WingManual::PrintHandlerPrototype(Class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
UWingServer::Printf(TEXT("\n"));
|
||||
EmitCommandList(false);
|
||||
// UWingServer::Print(TEXT("\n--- Half-Baked (may have issues) ---\n\n"));
|
||||
// EmitCommandList(true);
|
||||
UWingServer::Printf(TEXT("\n"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingFetcher.h"
|
||||
#include "WingManual.h"
|
||||
#include "UserManual.generated.h"
|
||||
|
||||
UCLASS()
|
||||
@@ -19,78 +18,6 @@ public:
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingFetcher::PrintPathExplanation();
|
||||
UWingServer::Print(TEXT(
|
||||
"\n TYPES:"
|
||||
"\n"
|
||||
"\n To change variable types, or function prototypes, you will"
|
||||
"\n use our syntax for types. Here are some simple examples:"
|
||||
"\n"
|
||||
"\n boolean, int64, double, string, etc."
|
||||
"\n vector, rotator, hitresult, etc."
|
||||
"\n actor, character, playercontroller, etc."
|
||||
"\n eblendmode, emovementmode, etc."
|
||||
"\n"
|
||||
"\n Notice that it's 'actor', not 'AActor'."
|
||||
"\n You can use the following notations for complex types:"
|
||||
"\n"
|
||||
"\n Soft<abp_manny>, Class<pawn>, SoftClass<pawn>"
|
||||
"\n Array<int>, Set<string>, Map<int,string>"
|
||||
"\n"
|
||||
"\n FUNCTION ARGUMENTS AND RETURN VALUES:"
|
||||
"\n"
|
||||
"\n Function argument lists are expressed as comma-separated"
|
||||
"\n lists of type-name pairs:"
|
||||
"\n"
|
||||
"\n double D,PlayerController P,Array<int> A"
|
||||
"\n"
|
||||
"\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 You can view the arguments using GraphNode_Dump. If a return "
|
||||
"\n node doesn't exist, you may have to create it using GraphNode_Create"
|
||||
"\n before you can set return values. Custom event nodes also have"
|
||||
"\n editable arguments."
|
||||
"\n"
|
||||
"\n IDENTIFIER SANITIZATION:\n"
|
||||
"\n"
|
||||
"\n Identifiers in Unreal can contain spaces and punctuation marks.\n"
|
||||
"\n Those punctuation marks could confuse our parsers. For example,"
|
||||
"\n How would we parse Array<X> if the typename X contained a less-than?"
|
||||
"\n So, we automatically translate these characters on output:"
|
||||
"\n"
|
||||
"\n space -> ·"
|
||||
"\n < -> ◁"
|
||||
"\n > -> ▷"
|
||||
"\n , -> ▾"
|
||||
"\n "
|
||||
"\n We do the reverse translation on input. Therefore, you will always"
|
||||
"\n see sanitized versions of identifiers, and you must always use"
|
||||
"\n sanitized versions of identifiers:"
|
||||
"\n"
|
||||
"\n Correct: /Game/Testing/BP_Test,graph:Get·Cursor·Location"
|
||||
"\n Wrong: /Game/Testing/BP_Test,graph:Get Cursor Location"
|
||||
"\n"
|
||||
"\n ABOUT WHITESPACE:"
|
||||
"\n"
|
||||
"\n Do not put excess whitespace into paths, typenames, or"
|
||||
"\n function prototypes, only use whitespace where it is required"
|
||||
"\n by the syntax."
|
||||
"\n"
|
||||
"\n MATERIAL EDITING:"
|
||||
"\n"
|
||||
"\n We do not expose material expressions directly. Instead, you"
|
||||
"\n will be editing the material graph. However, if you Graph_Dump"
|
||||
"\n a material graph, you will see that the nodes contain mxprop"
|
||||
"\n properties, which actually come from the material expressions."
|
||||
"\n You can edit these using Property_Set on the node."
|
||||
"\n"
|
||||
"\n COMMANDS YOU SHOULD KNOW ABOUT AND REMEMBER:"
|
||||
"\n"
|
||||
"\n UserManual: this explanation"
|
||||
"\n ShowCommands: a full list of all the commands"
|
||||
"\n Graph_Dump: a detailed listing of any UEdGraph"
|
||||
"\n Property_Dump: show information on many objects"
|
||||
"\n"
|
||||
));
|
||||
WingManual::PrintManual(WingManual::AllSections(), nullptr, false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -18,38 +18,8 @@
|
||||
#include "Blueprint/WidgetTree.h"
|
||||
#include "Components/Widget.h"
|
||||
#include "Subsystems/AssetEditorSubsystem.h"
|
||||
|
||||
void WingFetcher::PrintPathExplanation()
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n PATHS:"
|
||||
"\n"
|
||||
"\n Most commands require you to specify a path. A path starts"
|
||||
"\n with an asset name, followed by steps separated by ,"
|
||||
"\n that navigate into the asset. Some Examples:"
|
||||
"\n"
|
||||
"\n /Game/Widgets/WB_Hotkeys,graph:EventGraph,node:Self03,pin:Result"
|
||||
"\n /Game/Testing/BP_Test,graph:Clear·Action·Grid,node:K2Node_CallFunction_0"
|
||||
"\n"
|
||||
"\n The navigation steps supported are:"
|
||||
"\n"
|
||||
"\n graph — move from a blueprint or material to a graph."
|
||||
"\n node — move from a graph to a graph node"
|
||||
"\n pin — move from a graph node to a pin"
|
||||
"\n component — move from a blueprint to a component"
|
||||
"\n levelblueprint — move from a world to a blueprint"
|
||||
"\n widget — move from a widget blueprint to a widget"
|
||||
"\n"
|
||||
"\n Notice that paths use sanitized identifiers. See the UserManual"
|
||||
"\n for more information on name sanitization."
|
||||
"\n"
|
||||
"\n Steps do not always require a parameter. For example, materials"
|
||||
"\n only have one graph, so you can just say:"
|
||||
"\n"
|
||||
"\n /Game/Materials/MyMaterial,graph"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
#include "WingServer.h"
|
||||
#include "WingManual.h"
|
||||
|
||||
WingFetcher::WalkFunc WingFetcher::GetWalker(const FString& Step)
|
||||
{
|
||||
@@ -94,7 +64,7 @@ void WingFetcher::PathFailed(const TCHAR* Expected)
|
||||
UWingServer::Printf(TEXT("ERROR: Path specifies a %s, but expected %s\n"), *Obj->GetClass()->GetName(), Expected);
|
||||
else
|
||||
UWingServer::Printf(TEXT("ERROR: Path led to a null pointer\n"));
|
||||
PrintPathExplanation();
|
||||
UWingServer::SuggestManual(WingManual::Section::Paths);
|
||||
SetError();
|
||||
}
|
||||
|
||||
@@ -106,7 +76,7 @@ WingFetcher& WingFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expecte
|
||||
UWingServer::Printf(TEXT("ERROR: Input to '%s' is %s, but expected %s\n"), Walker, *Obj->GetClass()->GetName(), Expected);
|
||||
else
|
||||
UWingServer::Printf(TEXT("ERROR: Path led to a null pointer\n"));
|
||||
PrintPathExplanation();
|
||||
UWingServer::SuggestManual(WingManual::Section::Paths);
|
||||
SetError();
|
||||
return *this;
|
||||
}
|
||||
@@ -115,11 +85,19 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
if (Path.Contains(TEXT(" ")))
|
||||
{
|
||||
UWingServer::Printf(TEXT("ERROR: Paths may not contain whitespace."));
|
||||
UWingServer::SuggestManual(WingManual::Section::Paths);
|
||||
UWingServer::SuggestManual(WingManual::Section::IdentifierSanitization);
|
||||
return SetError();
|
||||
}
|
||||
TArray<FString> Segments;
|
||||
Path.ParseIntoArray(Segments, TEXT(","));
|
||||
if (Segments.Num() == 0)
|
||||
{
|
||||
UWingServer::Print(TEXT("ERROR: Empty path\n"));
|
||||
UWingServer::SuggestManual(WingManual::Section::Paths);
|
||||
return SetError();
|
||||
}
|
||||
|
||||
@@ -140,6 +118,7 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
|
||||
if (!Func)
|
||||
{
|
||||
UWingServer::Printf(TEXT("ERROR: Unknown path step '%s'\n"), *Key);
|
||||
UWingServer::SuggestManual(WingManual::Section::Paths);
|
||||
return SetError();
|
||||
}
|
||||
(this->*Func)(Value);
|
||||
@@ -155,8 +134,8 @@ WingFetcher& WingFetcher::Asset(const FString& PackagePath)
|
||||
|
||||
if (!PackagePath.StartsWith(TEXT("/")))
|
||||
{
|
||||
UWingServer::Printf(TEXT("ERROR: Asset path must start with '/', got '%s'\n"), *PackagePath);
|
||||
PrintPathExplanation();
|
||||
UWingServer::Printf(TEXT("ERROR: Path must start with '/', got '%s'\n"), *PackagePath);
|
||||
UWingServer::SuggestManual(WingManual::Section::Paths);
|
||||
return SetError();
|
||||
}
|
||||
|
||||
@@ -224,7 +203,7 @@ WingFetcher& WingFetcher::Graph(const FString& Value)
|
||||
if (!Value.IsEmpty())
|
||||
{
|
||||
UWingServer::Printf(TEXT("ERROR: Materials have only one graph, with a blank name.\n\n"));
|
||||
PrintPathExplanation();
|
||||
UWingServer::SuggestManual(WingManual::Section::Paths);
|
||||
return SetError();
|
||||
}
|
||||
WingUtils::EnsureMaterialGraph(Mat);
|
||||
|
||||
@@ -341,7 +341,6 @@ void WingGraphExport::EmitComments()
|
||||
|
||||
// Emit wrapped, indented body.
|
||||
Output.Append(WingUtils::WrapText(CommentNode->NodeComment, 70, TEXT(" - ")));
|
||||
Output.Append(TEXT("\n"));
|
||||
|
||||
// Find contained nodes.
|
||||
TArray<FString> ContainedNames;
|
||||
|
||||
305
Plugins/UEWingman/Source/UEWingman/Private/WingManual.cpp
Normal file
305
Plugins/UEWingman/Source/UEWingman/Private/WingManual.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
#include "WingManual.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingTypes.h"
|
||||
|
||||
TSet<WingManual::Section> WingManual::AllSections()
|
||||
{
|
||||
return {
|
||||
Section::Paths,
|
||||
Section::Types,
|
||||
Section::FunctionArguments,
|
||||
Section::IdentifierSanitization,
|
||||
Section::Whitespace,
|
||||
Section::MaterialEditing,
|
||||
Section::ImportantCommands,
|
||||
};
|
||||
}
|
||||
|
||||
void WingManual::PrintHandlerPrototype(UClass *HandlerClass)
|
||||
{
|
||||
UWingServer::Print(WingUtils::GetHandlerName(HandlerClass));
|
||||
UWingServer::Print(TEXT("("));
|
||||
bool bFirst = true;
|
||||
for (TFieldIterator<FProperty> PropIt(HandlerClass, EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||
{
|
||||
if (!bFirst) UWingServer::Print(TEXT(","));
|
||||
bFirst = false;
|
||||
if (PropIt->HasMetaData(TEXT("Optional"))) UWingServer::Print(TEXT("?"));
|
||||
UWingServer::Print(PropIt->GetName());
|
||||
}
|
||||
UWingServer::Print(TEXT(")\n"));
|
||||
}
|
||||
|
||||
void WingManual::PrintHandlerArguments(UClass *HandlerClass)
|
||||
{
|
||||
// parameter details
|
||||
for (TFieldIterator<FProperty> PropIt(HandlerClass, EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||
{
|
||||
FProperty* Prop = *PropIt;
|
||||
FString Name = Prop->GetName();
|
||||
FString Type = UWingTypes::TypeToText(Prop);
|
||||
bool bOptional = Prop->HasMetaData(TEXT("Optional"));
|
||||
const FString& Desc = Prop->GetMetaData(TEXT("Description"));
|
||||
|
||||
if (bOptional)
|
||||
{
|
||||
UWingServer::Printf(TEXT(" %s (optional %s)"), *Name, *Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
UWingServer::Printf(TEXT(" %s (%s)"), *Name, *Type);
|
||||
}
|
||||
if (!Desc.IsEmpty()) UWingServer::Printf(TEXT(" — %s"), *Desc);
|
||||
UWingServer::Print(TEXT("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
void WingManual::PrintHandlerDescription(UClass *HandlerClass)
|
||||
{
|
||||
const IWingHandler* Handler = Cast<IWingHandler>(HandlerClass->GetDefaultObject());
|
||||
if (!Handler) return;
|
||||
UWingServer::Print(WingUtils::WrapText(Handler->GetDescription(), 80, TEXT(" // ")));
|
||||
}
|
||||
|
||||
void WingManual::PrintHandlerHelp(UClass* HandlerClass)
|
||||
{
|
||||
UWingServer::Print(TEXT("\n"));
|
||||
PrintHandlerPrototype(HandlerClass);
|
||||
PrintHandlerArguments(HandlerClass);
|
||||
PrintHandlerDescription(HandlerClass);
|
||||
UWingServer::Print(TEXT("\n"));
|
||||
}
|
||||
|
||||
void WingManual::PrintManual(TSet<Section> Sections, UClass *Handler, bool Abridged)
|
||||
{
|
||||
if (Handler == nullptr)
|
||||
{
|
||||
Sections.Remove(Section::HandlerHelp);
|
||||
}
|
||||
|
||||
if (Sections.IsEmpty()) return;
|
||||
|
||||
if (Abridged)
|
||||
{
|
||||
UWingServer::Printf(TEXT("\n--- AUTOMATIC DOCUMENTATION ---\n"));
|
||||
}
|
||||
|
||||
if (Sections.Contains(Section::HandlerHelp))
|
||||
{
|
||||
PrintHandlerHelp(Handler);
|
||||
}
|
||||
|
||||
if (Sections.Contains(Section::Paths))
|
||||
{
|
||||
if (Abridged)
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n PATHS: Here are some example paths:"
|
||||
"\n /Game/Widgets/WB_Hotkeys,widget:Canvas·122"
|
||||
"\n /Game/Testing/BP_Test,graph:Rescale·Actor,node:K2Node_CallFunction_0,pin:Scale"
|
||||
"\n /Game/Chars/BP_Manny,component:Camera·Boom"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n PATHS:"
|
||||
"\n"
|
||||
"\n Most commands require you to specify a path. A path starts"
|
||||
"\n with an asset name, followed by steps separated by ,"
|
||||
"\n that navigate into the asset. Some Examples:"
|
||||
"\n"
|
||||
"\n /Game/Widgets/WB_Hotkeys,widget:Canvas·122"
|
||||
"\n /Game/Testing/BP_Test,graph:Rescale·Actor,node:K2Node_CallFunction_0,pin:Scale"
|
||||
"\n /Game/Chars/BP_Manny,component:Camera·Boom"
|
||||
"\n"
|
||||
"\n The navigation steps supported are:"
|
||||
"\n"
|
||||
"\n graph — move from a blueprint or material to a graph."
|
||||
"\n node — move from a graph to a graph node"
|
||||
"\n pin — move from a graph node to a pin"
|
||||
"\n component — move from a blueprint to a component"
|
||||
"\n levelblueprint — move from a world to a blueprint"
|
||||
"\n widget — move from a widget blueprint to a widget"
|
||||
"\n"
|
||||
"\n Notice that paths use sanitized identifiers. See the section"
|
||||
"\n on identifier sanitization below for more information."
|
||||
"\n"
|
||||
"\n Steps do not always require a parameter. For example, materials"
|
||||
"\n only have one graph, so you can just say:"
|
||||
"\n"
|
||||
"\n /Game/Materials/MyMaterial,graph"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (Sections.Contains(Section::Types))
|
||||
{
|
||||
if (Abridged)
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n TYPES: Here are some examples of valid types:"
|
||||
"\n Bool, String, Vector, Rotator, HitResult, Actor, Character,"
|
||||
"\n PlayerController, EBlendMode, EMovementMode, BP_Manny, BP_Quinn,"
|
||||
"\n Array<Int>, Set<String>, Map<Int,Actor>"
|
||||
"\n Soft<ABP_Manny>, Class<Pawn>, SoftClass<Pawn>"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n TYPES:"
|
||||
"\n"
|
||||
"\n To change variable types, or to express function prototypes, you will"
|
||||
"\n use our syntax for types. Here are some valid examples:"
|
||||
"\n"
|
||||
"\n Bool, String, Vector, Rotator, HitResult, Actor, Character,"
|
||||
"\n PlayerController, EBlendMode, EMovementMode, BP_Manny, BP_Quinn,"
|
||||
"\n Array<Int>, Set<String>, Map<Int,Actor>"
|
||||
"\n Soft<ABP_Manny>, Class<Pawn>, SoftClass<Pawn>"
|
||||
"\n"
|
||||
"\n Notice that it's 'Actor', not 'AActor'. Type names are not"
|
||||
"\n case-sensitive. When a blueprint like /Game/Testing/BP_Foo"
|
||||
"\n is used as a type, the typename is BP_Foo."
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (Sections.Contains(Section::FunctionArguments))
|
||||
{
|
||||
if (Abridged)
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n FUNCTION ARGUMENTS: Here is an example argument list:"
|
||||
"\n double D,PlayerController P,Array<Int> A"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n FUNCTION ARGUMENTS AND RETURN VALUES:"
|
||||
"\n"
|
||||
"\n Function argument lists are expressed as comma-separated"
|
||||
"\n lists of type-name pairs:"
|
||||
"\n"
|
||||
"\n Double D,PlayerController P,Array<Int> A"
|
||||
"\n"
|
||||
"\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 You can view the arguments using GraphNode_Dump. If a return "
|
||||
"\n node doesn't exist, you may have to create it using GraphNode_Create"
|
||||
"\n before you can set return values. Custom event nodes also have"
|
||||
"\n editable arguments."
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (Sections.Contains(Section::IdentifierSanitization))
|
||||
{
|
||||
if (Abridged)
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n IDENTIFIER SANITIZATION:\n"
|
||||
"\n Identifiers in unreal can contain whitespace and punctuation.\n"
|
||||
"\n We sanitize these characters on output:\n"
|
||||
"\n"
|
||||
"\n space → ·"
|
||||
"\n < → ◁"
|
||||
"\n > → ▷"
|
||||
"\n , → ▾"
|
||||
"\n "
|
||||
"\n We do the reverse translation on input. Therefore, you will always"
|
||||
"\n see sanitized versions of identifiers, and you must always use"
|
||||
"\n sanitized versions of identifiers:"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n IDENTIFIER SANITIZATION:\n"
|
||||
"\n"
|
||||
"\n Identifiers in Unreal can contain spaces and punctuation marks.\n"
|
||||
"\n Those punctuation marks could confuse our parsers. For example,"
|
||||
"\n How would we parse Array<X> if the typename X contained a less-than?"
|
||||
"\n So, we automatically translate these characters on output:"
|
||||
"\n"
|
||||
"\n space → ·"
|
||||
"\n < → ◁"
|
||||
"\n > → ▷"
|
||||
"\n , → ▾"
|
||||
"\n "
|
||||
"\n We do the reverse translation on input. Therefore, you will always"
|
||||
"\n see sanitized versions of identifiers, and you must always use"
|
||||
"\n sanitized versions of identifiers:"
|
||||
"\n"
|
||||
"\n Correct: /Game/Testing/BP_Test,graph:Get·Cursor·Location"
|
||||
"\n Wrong: /Game/Testing/BP_Test,graph:Get Cursor Location"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (Sections.Contains(Section::Whitespace))
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n ABOUT WHITESPACE:"
|
||||
"\n Do not put excess whitespace into paths, typenames, or"
|
||||
"\n function prototypes, only use whitespace where it is required"
|
||||
"\n by the syntax."
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
|
||||
if (Sections.Contains(Section::MaterialEditing))
|
||||
{
|
||||
if (Abridged)
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n MATERIAL EDITING:"
|
||||
"\n We do not expose material expressions directly. Instead, use"
|
||||
"\n Property_Dump and Property_Set on the material graph nodes to"
|
||||
"\n edit material expression properties."
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n MATERIAL EDITING:"
|
||||
"\n"
|
||||
"\n We do not expose material expressions directly. Instead, you"
|
||||
"\n will be editing the material graph. However, if you Graph_Dump"
|
||||
"\n a material graph, you will see that the nodes contain mxprop"
|
||||
"\n properties, which actually come from the material expressions."
|
||||
"\n You can edit these using Property_Set on the node."
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (Sections.Contains(Section::ImportantCommands))
|
||||
{
|
||||
UWingServer::Print(TEXT(
|
||||
"\n COMMANDS YOU SHOULD KNOW ABOUT AND REMEMBER:"
|
||||
"\n UserManual: this explanation"
|
||||
"\n ShowCommands: a full list of all the commands"
|
||||
"\n Blueprint_Dump: a summary of any blueprint"
|
||||
"\n Graph_Dump: a fairly detailed listing of any Graph"
|
||||
"\n Property_Dump: show information on many objects"
|
||||
"\n"
|
||||
));
|
||||
}
|
||||
|
||||
if (Abridged)
|
||||
{
|
||||
UWingServer::Printf(TEXT("\nUse command 'UserManual' to see the full manual.\n"));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingManual.h"
|
||||
#include "WingJson.h"
|
||||
#include "WingLogCapture.h"
|
||||
#include "WingUtils.h"
|
||||
@@ -263,6 +264,8 @@ FString UWingServer::HandleRequest(const FString& Line)
|
||||
LogCapture.CapturedErrors.Empty();
|
||||
LogCapture.bEnabled = true;
|
||||
HandlerOutput.Reset();
|
||||
SuggestedManualSections.Empty();
|
||||
LastHandlerClass = nullptr;
|
||||
|
||||
TryCallHandler(Line);
|
||||
|
||||
@@ -273,6 +276,11 @@ FString UWingServer::HandleRequest(const FString& Line)
|
||||
UWingServer::Printf(TEXT("UE_LOG: %s\n"), *Msg);
|
||||
}
|
||||
LogCapture.CapturedErrors.Empty();
|
||||
if (!SuggestedManualSections.IsEmpty())
|
||||
{
|
||||
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
|
||||
WingManual::PrintManual(SuggestedManualSections, LastHandlerClass, true);
|
||||
}
|
||||
FString Result = HandlerOutput.ToString();
|
||||
HandlerOutput.Reset();
|
||||
for (int32 i = 0; i < Result.Len(); ++i)
|
||||
@@ -299,6 +307,7 @@ void UWingServer::TryCallHandler(const FString &Line)
|
||||
if (!Request->TryGetStringField(TEXT("command"), Command))
|
||||
{
|
||||
UWingServer::Printf(TEXT("Request does not contain 'command' parameter"));
|
||||
UWingServer::Printf(TEXT("We recommend sending command='UserManual'."));
|
||||
return;
|
||||
}
|
||||
Request->RemoveField(TEXT("command"));
|
||||
@@ -308,8 +317,10 @@ void UWingServer::TryCallHandler(const FString &Line)
|
||||
if (!HandlerClass)
|
||||
{
|
||||
UWingServer::Printf(TEXT("Unknown command: %s"), *Command);
|
||||
UWingServer::SuggestManual(WingManual::Section::ImportantCommands);
|
||||
return;
|
||||
}
|
||||
LastHandlerClass = *HandlerClass;
|
||||
|
||||
// Make an object of the handler class.
|
||||
TStrongObjectPtr<UObject> HandlerObj(NewObject<UObject>(GetTransientPackage(), *HandlerClass));
|
||||
@@ -318,8 +329,7 @@ void UWingServer::TryCallHandler(const FString &Line)
|
||||
// Populate the handler object with the request parameters.
|
||||
if (!WingJson::PopulateFromJson(HandlerObj->GetClass(), HandlerObj.Get(), &*Request))
|
||||
{
|
||||
UWingServer::Printf(TEXT("\nUsage:\n\n"));
|
||||
WingUtils::PrintHandlerHelp(*HandlerClass);
|
||||
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -302,16 +302,14 @@ FString WingUtils::FormatNodeTitle(const UEdGraphNode *Node)
|
||||
|
||||
FString WingUtils::WrapText(const FString& Text, int32 ColLimit, const FString& Prefix)
|
||||
{
|
||||
FString Clean = Text;
|
||||
Clean.ReplaceInline(TEXT("\r\n"), TEXT("\n"));
|
||||
TArray<FString> Words;
|
||||
Clean.ParseIntoArrayWS(Words);
|
||||
Text.ParseIntoArrayWS(Words);
|
||||
|
||||
TStringBuilder<1024> Result;
|
||||
int32 Col = 0;
|
||||
for (const FString& Word : Words)
|
||||
{
|
||||
if (Col > 0 && Col + 1 + Word.Len() > ColLimit)
|
||||
if ((Col > 0) && (Col + 1 + Word.Len() > ColLimit))
|
||||
{
|
||||
Result.Append(TEXT("\n"));
|
||||
Col = 0;
|
||||
@@ -319,16 +317,17 @@ FString WingUtils::WrapText(const FString& Text, int32 ColLimit, const FString&
|
||||
if (Col == 0)
|
||||
{
|
||||
Result.Append(Prefix);
|
||||
Col = Prefix.Len();
|
||||
Result.Append(Word);
|
||||
Col = Prefix.Len() + Word.Len();
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
Result.Append(TEXT(" "));
|
||||
Col += 1;
|
||||
Result.Append(Word);
|
||||
Col = 1 + Word.Len();
|
||||
}
|
||||
Result.Append(Word);
|
||||
Col += Word.Len();
|
||||
}
|
||||
if (Col > 0) Result.Append(TEXT("\n"));
|
||||
return Result.ToString();
|
||||
}
|
||||
|
||||
@@ -616,48 +615,3 @@ FString WingUtils::GetHandlerGroup(UClass* HandlerClass)
|
||||
return Name.Left(UnderscoreIdx);
|
||||
return Name;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// PrintHandlerHelp — verbose description of one handler command
|
||||
// ============================================================
|
||||
|
||||
void WingUtils::PrintHandlerHelp(UClass* HandlerClass)
|
||||
{
|
||||
const IWingHandler* Handler = Cast<IWingHandler>(HandlerClass->GetDefaultObject());
|
||||
if (!Handler) return;
|
||||
|
||||
FString ToolName = GetHandlerName(HandlerClass);
|
||||
|
||||
UWingServer::Print(TEXT("\n"));
|
||||
UWingServer::Print(WrapText(Handler->GetDescription(), 80, TEXT("// ")));
|
||||
UWingServer::Print(TEXT("\n"));
|
||||
|
||||
// Command signature line
|
||||
UWingServer::Print(ToolName);
|
||||
UWingServer::Print(TEXT("("));
|
||||
bool bFirst = true;
|
||||
for (TFieldIterator<FProperty> PropIt(HandlerClass, EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||
{
|
||||
if (!bFirst) UWingServer::Print(TEXT(","));
|
||||
bFirst = false;
|
||||
if (PropIt->HasMetaData(TEXT("Optional"))) UWingServer::Print(TEXT("?"));
|
||||
UWingServer::Print(PropIt->GetName());
|
||||
}
|
||||
UWingServer::Print(TEXT(")\n"));
|
||||
|
||||
// parameter details
|
||||
for (TFieldIterator<FProperty> PropIt(HandlerClass, EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||
{
|
||||
FProperty* Prop = *PropIt;
|
||||
FString Name = Prop->GetName();
|
||||
FString Type = UWingTypes::TypeToText(Prop);
|
||||
bool bOptional = Prop->HasMetaData(TEXT("Optional"));
|
||||
const FString& Desc = Prop->GetMetaData(TEXT("Description"));
|
||||
|
||||
UWingServer::Printf(TEXT(" %s %s%s"),
|
||||
*Type, *Name, bOptional ? TEXT(" (optional)") : TEXT(""));
|
||||
if (!Desc.IsEmpty())
|
||||
UWingServer::Printf(TEXT(" — %s"), *Desc);
|
||||
UWingServer::Print(TEXT("\n"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,7 @@ struct FWalker;
|
||||
|
||||
class WingFetcher
|
||||
{
|
||||
public:
|
||||
// Print a general explanation of what paths look like.
|
||||
static void PrintPathExplanation();
|
||||
|
||||
public:
|
||||
// Walk a path from an asset to an object
|
||||
// within that asset. If you call walk a
|
||||
// second time, it will walk additional steps.
|
||||
|
||||
25
Plugins/UEWingman/Source/UEWingman/Public/WingManual.h
Normal file
25
Plugins/UEWingman/Source/UEWingman/Public/WingManual.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "Containers/Set.h"
|
||||
|
||||
class WingManual
|
||||
{
|
||||
public:
|
||||
enum class Section
|
||||
{
|
||||
HandlerHelp,
|
||||
Paths,
|
||||
Types,
|
||||
FunctionArguments,
|
||||
IdentifierSanitization,
|
||||
Whitespace,
|
||||
MaterialEditing,
|
||||
ImportantCommands,
|
||||
};
|
||||
|
||||
static TSet<Section> AllSections();
|
||||
static void PrintHandlerPrototype(UClass *Handler);
|
||||
static void PrintHandlerArguments(UClass *Handler);
|
||||
static void PrintHandlerDescription(UClass *Handler);
|
||||
static void PrintHandlerHelp(UClass *Handler);
|
||||
static void PrintManual(TSet<Section> Sections, UClass *Handler, bool Abridged);
|
||||
};
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "WingUtils.h"
|
||||
#include "WingNotifier.h"
|
||||
#include "WingLogCapture.h"
|
||||
#include "WingManual.h"
|
||||
#include "WingServer.generated.h"
|
||||
|
||||
class FSocket;
|
||||
@@ -59,6 +60,9 @@ public:
|
||||
GWingServer->HandlerOutput.Appendf(Fmt, Forward<ArgTypes>(Args)...);
|
||||
}
|
||||
|
||||
/** Suggest that a manual section be printed after the handler finishes. */
|
||||
static void SuggestManual(WingManual::Section Section) { GWingServer->SuggestedManualSections.Add(Section); }
|
||||
|
||||
/** Whether the server is currently listening. */
|
||||
bool IsRunning() const { return bRunning; }
|
||||
|
||||
@@ -71,7 +75,9 @@ private:
|
||||
// ----- Tool dispatch -----
|
||||
UPROPERTY()
|
||||
FWingNotifier Notifier;
|
||||
UClass* LastHandlerClass;
|
||||
TStringBuilder<16384> HandlerOutput;
|
||||
TSet<WingManual::Section> SuggestedManualSections;
|
||||
FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request
|
||||
UPROPERTY()
|
||||
TMap<FString, TObjectPtr<UClass>> WingHandlerRegistry; // tool name -> UWingHandler subclass
|
||||
|
||||
@@ -230,7 +230,6 @@ public:
|
||||
static TArray<UClass*> CollectHandlerClasses();
|
||||
static FString GetHandlerName(UClass* HandlerClass);
|
||||
static FString GetHandlerGroup(UClass* HandlerClass);
|
||||
static void PrintHandlerHelp(UClass* HandlerClass);
|
||||
|
||||
// ----- Reparent validation -----
|
||||
static bool CanReparentBlueprint(UClass* CurrentGenerated, UClass* Proposed);
|
||||
|
||||
Reference in New Issue
Block a user