Overhaul of manual-generation code

This commit is contained in:
2026-04-13 21:33:02 -04:00
parent 1db2705877
commit 34011e43d5
8 changed files with 235 additions and 264 deletions

View File

@@ -12,6 +12,9 @@ class UWing_Documentation_Manual : public UWingHandler
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(EditAnywhere, meta=(Optional, Description="If specified, print only this section of the manual"))
FString Section;
virtual void Register() override virtual void Register() override
{ {
UWingServer::AddHandler(this, UWingServer::AddHandler(this,
@@ -19,6 +22,23 @@ public:
} }
virtual void Handle() override virtual void Handle() override
{ {
WingManual::PrintManual({WingManual::Section::All}, nullptr, false); if (Section.IsEmpty())
{
UWingManualSections::FetcherPaths();
UWingManualSections::ExpressingTypes();
UWingManualSections::VariableDeclarations();
UWingManualSections::EscapeSequencesInFNames();
UWingManualSections::MaterialEditing();
UWingManualSections::ImportantCommands();
}
else
{
FName SectionName(*Section);
if (!WingManual::PrintSection(SectionName))
{
WingOut::Stdout.Printf(TEXT("ERROR: Unknown manual section '%s'\nAvailable sections: "), *Section);
WingManual::PrintSectionNames(WingManual::GetSections());
}
}
} }
}; };

View File

@@ -53,7 +53,7 @@ void WingFetcher::PathFailed(const TCHAR* Expected)
Errors.Printf(TEXT("ERROR: Path specifies a %s, but expected %s\n"), *Obj.Get()->GetClass()->GetName(), Expected); Errors.Printf(TEXT("ERROR: Path specifies a %s, but expected %s\n"), *Obj.Get()->GetClass()->GetName(), Expected);
else else
Errors.Printf(TEXT("ERROR: Path led to a null pointer\n")); Errors.Printf(TEXT("ERROR: Path led to a null pointer\n"));
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
SetError(); SetError();
} }
@@ -63,7 +63,7 @@ WingFetcher& WingFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expecte
Errors.Printf(TEXT("ERROR: Input to '%s' is %s, but expected %s\n"), Walker, *Obj.Get()->GetClass()->GetName(), Expected); Errors.Printf(TEXT("ERROR: Input to '%s' is %s, but expected %s\n"), Walker, *Obj.Get()->GetClass()->GetName(), Expected);
else else
Errors.Printf(TEXT("ERROR: Path led to a null pointer\n")); Errors.Printf(TEXT("ERROR: Path led to a null pointer\n"));
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
SetError(); SetError();
return *this; return *this;
} }
@@ -75,8 +75,8 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
if (Path.Contains(TEXT(" "))) if (Path.Contains(TEXT(" ")))
{ {
Errors.Printf(TEXT("ERROR: Paths may not contain whitespace.")); Errors.Printf(TEXT("ERROR: Paths may not contain whitespace."));
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
UWingServer::SuggestManual(WingManual::Section::EscapeSequences); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, EscapeSequencesInFNames));
return SetError(); return SetError();
} }
TArray<FString> Segments; TArray<FString> Segments;
@@ -84,7 +84,7 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
if (Segments.Num() == 0) if (Segments.Num() == 0)
{ {
Errors.Print(TEXT("ERROR: Empty path\n")); Errors.Print(TEXT("ERROR: Empty path\n"));
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
return SetError(); return SetError();
} }
@@ -105,7 +105,7 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
if (!Func) if (!Func)
{ {
Errors.Printf(TEXT("ERROR: Unknown path step '%s'\n"), *Key); Errors.Printf(TEXT("ERROR: Unknown path step '%s'\n"), *Key);
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
return SetError(); return SetError();
} }
(this->*Func)(Value); (this->*Func)(Value);
@@ -122,7 +122,7 @@ WingFetcher& WingFetcher::Asset(const FString& PackagePath)
if (!PackagePath.StartsWith(TEXT("/"))) if (!PackagePath.StartsWith(TEXT("/")))
{ {
Errors.Printf(TEXT("ERROR: Path must start with '/', got '%s'\n"), *PackagePath); Errors.Printf(TEXT("ERROR: Path must start with '/', got '%s'\n"), *PackagePath);
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
return SetError(); return SetError();
} }
@@ -182,7 +182,7 @@ WingFetcher& WingFetcher::Graph(const FString& Value)
if (!Value.IsEmpty()) if (!Value.IsEmpty())
{ {
Errors.Printf(TEXT("ERROR: Materials have only one graph, with a blank name.\n\n")); Errors.Printf(TEXT("ERROR: Materials have only one graph, with a blank name.\n\n"));
UWingServer::SuggestManual(WingManual::Section::Paths); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
return SetError(); return SetError();
} }
WingUtils::EnsureMaterialGraph(Mat); WingUtils::EnsureMaterialGraph(Mat);

View File

@@ -58,241 +58,168 @@ void WingManual::PrintHandlerHelp(const FWingHandlerConfig& Handler)
WingOut::Stdout.Print(TEXT("\n")); WingOut::Stdout.Print(TEXT("\n"));
} }
void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* Handler, bool Abridged) void UWingManualSections::FetcherPaths()
{ {
if (Sections.IsEmpty()) return; WingOut::Stdout.Print(TEXT(
"\n FETCHER PATHS:"
"\n"
"\n Most commands require you to specify a 'fetcher path'."
"\n A fetcher path starts with an asset name, followed by"
"\n steps 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 structprop — move into a struct property of an object"
"\n"
"\n Notice that paths use escaped fnames. See the section"
"\n on escape sequences in fnames below sfor 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"
));
}
const bool bPrintAll = Sections.Contains(Section::All); void UWingManualSections::ExpressingTypes()
{
WingOut::Stdout.Print(TEXT(
"\n EXPRESSING TYPES:"
"\n"
"\n To change the type of a variable, or to express function parameters,"
"\n you will 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 just BP_Foo. You can search"
"\n for valid types using the TypeName_Search command."
"\n"
));
}
if (Abridged) void UWingManualSections::VariableDeclarations()
{ {
WingOut::Stdout.Printf(TEXT("\n--- AUTOMATIC DOCUMENTATION ---\n")); WingOut::Stdout.Print(TEXT(
} "\n VARIABLE DECLARATIONS:"
"\n"
"\n We have our own syntax for variable declarations: a type,"
"\n a name, optional flags, and an optional default value,"
"\n always on one line:"
"\n"
"\n Array<Actor> Actors"
"\n Float F (InstanceEditable)"
"\n String S = This is the default value"
"\n"
"\n The commands Variables_Add, Variables_Modify,"
"\n and Variables_Remove can be used to edit "
"\n blueprint variables, graph local variables, graph input"
"\n variables, graph output variables, and custom"
"\n event node input variables. Event dispatchers are"
"\n also graphs, so they too can be edited."
"\n"
));
}
if (Handler && (Sections.Contains(Section::HandlerHelp) || bPrintAll)) void UWingManualSections::EscapeSequencesInFNames()
{ {
PrintHandlerHelp(*Handler); WingOut::Stdout.Print(TEXT(
} "\n ESCAPE SEQUENCES IN FNAMES:"
"\n"
"\n When we output FNames, we use HTML escape sequences for the"
"\n following marks: \\\"'(),.:;<=>&, and for certain other characters."
"\n We also translate spaces to periods."
"\n"
"\n When sending FNames to UE Wingman, you *must* escape the marks"
"\n listed above, but you *may* escape any character. To send an FName"
"\n with a space in it, either use &#32; or a period."
"\n"
));
}
if (Sections.Contains(Section::Paths) || bPrintAll) void UWingManualSections::MaterialEditing()
{
WingOut::Stdout.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"
"\n properties which actually come from the material expressions."
"\n You can edit these using Details_Set on the node."
"\n"
"\n Don't overlook custom HLSL nodes. These can accomplish in\n"
"\n a single node what would otherwise take many.\n"
"\n"
));
}
void UWingManualSections::ImportantCommands()
{
WingOut::Stdout.Print(TEXT(
"\n IMPORTANT COMMANDS:"
"\n"
"\n Documentation_Manual: print manual sections"
"\n Documentation_Commands: a list of all the main commands"
"\n Documentation_CreateAssets: Additional commands that create new assets"
"\n Blueprint_Dump: a summary of any blueprint"
"\n Graph_Dump: a fairly detailed listing of any Graph"
"\n Details_Dump: Dump the details panel for a given object"
"\n Details_Set: Manipulate the details panel for a given object"
"\n"
"\n You can use Documentation_Commands(Query=Command,Verbose=true)"
"\n to get detailed help for a specific command."
"\n"
"\n You can use Documentation_Manual(Section=Name) to print out"
"\n a specific manual section."
"\n"
));
}
TSet<FName> WingManual::GetSections()
{
TSet<FName> Result;
for (TFieldIterator<UFunction> It(UWingManualSections::StaticClass(), EFieldIterationFlags::None); It; ++It)
{ {
if (Abridged) Result.Add(It->GetFName());
{
WingOut::Stdout.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
{
WingOut::Stdout.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"
));
}
} }
return Result;
}
if (Sections.Contains(Section::Types) || bPrintAll) void WingManual::PrintSectionNames(const TSet<FName>& Sections)
{
if (Sections.IsEmpty()) return;
bool bFirst = true;
for (const FName& Section : Sections)
{ {
if (Abridged) if (!bFirst) WingOut::Stdout.Print(TEXT(", "));
{ bFirst = false;
WingOut::Stdout.Print(TEXT( WingOut::Stdout.Printf(TEXT("%s"), *Section.ToString());
"\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
{
WingOut::Stdout.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"
));
}
} }
WingOut::Stdout.Print(TEXT("\n"));
}
if (Sections.Contains(Section::VariableDeclarations) || bPrintAll) bool WingManual::PrintSection(FName Section)
{ {
if (Abridged) UFunction* Func = UWingManualSections::StaticClass()->FindFunctionByName(Section);
{ if (!Func) return false;
WingOut::Stdout.Print(TEXT( UWingManualSections::StaticClass()->GetDefaultObject()->ProcessEvent(Func, nullptr);
"\n VARIABLE DECLARATIONS: example variable declarations:" return true;
"\n Array<Actor> Actors"
"\n Float F (InstanceEditable)"
"\n String S = This is the default value"
));
}
else
{
WingOut::Stdout.Print(TEXT(
"\n VARIABLE DECLARATIONS:"
"\n"
"\n We have our own syntax for variable declarations: a type,"
"\n a name, optional flags, and an optional default value,"
"\n always on one line:"
"\n"
"\n Array<Actor> Actors"
"\n Float F (InstanceEditable)"
"\n String S = This is the default value"
"\n"
"\n The commands Variables_Add, Variables_Modify,"
"\n and Variables_Remove can be used to edit: "
"\n blueprint variables, graph local variables, graph input"
"\n variables, graph output variables, and custom"
"\n event node input variables. Event dispatchers are"
"\n also graphs, so they too can be edited."
"\n"
));
}
}
if (Sections.Contains(Section::EscapeSequences) || bPrintAll)
{
if (Abridged)
{
WingOut::Stdout.Print(TEXT(
"\n USING HTML ESCAPE SEQUENCES:"
"\n When we output FNames, we use HTML escape sequences for the"
"\n following marks: \\\"'(),.:;<=>& We also escape control"
"\n characters, and some (but not all) unicode characters."
"\n We also translate spaces to periods. Examples:"
"\n"
"\n FName(TEXT(\"No_Change\")) --> No_Change"
"\n FName(TEXT(\"ίδιος\")) --> ίδιος"
"\n FName(TEXT(\"✡✢❄\")) --> &#10017;&#10018;&#10052;"
"\n FName(TEXT(\"Hello.There\")) --> Hello&period;There"
"\n FName(TEXT(\"Hello There\")) --> Hello.There"
"\n FName(TEXT(\"Hello\n\")) --> Hello&NewLine;"
"\n "
"\n When sending FNames to UE Wingman, you *must* escape the marks"
"\n listed above and control characters, but you *may* escape"
"\n any character. To send an FName with a space in it, either"
"\n use &#32; or a period."
"\n"
"\n Currently, this escaping *only* applies to FNames. It"
"\n doesn't work to use escapes in asset names or file names."
"\n"
));
}
else
{
WingOut::Stdout.Print(TEXT(
"\n USING HTML ESCAPE SEQUENCES:"
"\n When we output FNames, we use HTML escape sequences for the"
"\n following marks: \\\"'(),.:;<=>&, and for certain other characters."
"\n We also translate spaces to periods."
"\n"
"\n When sending FNames to UE Wingman, you *must* escape the marks"
"\n listed above, but you *may* escape any character. To send an FName"
"\n with a space in it, either use &#32; or a period."
"\n"
));
}
}
if (Sections.Contains(Section::Whitespace) || bPrintAll)
{
WingOut::Stdout.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) || bPrintAll)
{
if (Abridged)
{
WingOut::Stdout.Print(TEXT(
"\n MATERIAL EDITING:"
"\n We do not expose material expressions directly. Instead, use"
"\n Details_Dump and Details_Set on the material graph nodes to"
"\n edit material expression properties."
"\n"
));
}
else
{
WingOut::Stdout.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"
"\n properties which actually come from the material expressions."
"\n You can edit these using Details_Set on the node."
"\n"
));
}
}
if (Sections.Contains(Section::ImportantCommands) || bPrintAll)
{
WingOut::Stdout.Print(TEXT(
"\n COMMANDS YOU SHOULD KNOW ABOUT AND REMEMBER:"
"\n Documentation_Manual: this explanation"
"\n Documentation_Commands: a list of all the main commands"
"\n Documentation_CreateAssets: Additional commands that create new assets"
"\n Blueprint_Dump: a summary of any blueprint"
"\n Graph_Dump: a fairly detailed listing of any Graph"
"\n Details_Dump: Dump the details panel for a given object"
"\n Details_Set: Manipulate the details panel for a given object"
"\n"
"\n You can use Documentation_Commands(Query=SomeCommand,Verbose=true)"
"\n to get detailed help for a specific command."
"\n"
));
}
if (Abridged)
{
WingOut::Stdout.Printf(TEXT("\nUse command 'Documentation_Manual' to see the full manual.\n"));
}
} }
void WingManual::Commands(EWingHandlerKind Kind, const FString& Query, bool Verbose) void WingManual::Commands(EWingHandlerKind Kind, const FString& Query, bool Verbose)

View File

@@ -175,6 +175,7 @@ FString UWingServer::HandleRequest(const FString& Line)
LogCapture.bEnabled = true; LogCapture.bEnabled = true;
WingOut::StdoutBuffer.Reset(); WingOut::StdoutBuffer.Reset();
SuggestedManualSections.Empty(); SuggestedManualSections.Empty();
bSuggestHandlerHelp = false;
LastHandler = nullptr; LastHandler = nullptr;
TryCallHandler(Line); TryCallHandler(Line);
@@ -186,10 +187,14 @@ FString UWingServer::HandleRequest(const FString& Line)
WingOut::Stdout.Printf(TEXT("UE_LOG: %s\n"), *Msg); WingOut::Stdout.Printf(TEXT("UE_LOG: %s\n"), *Msg);
} }
LogCapture.CapturedErrors.Empty(); LogCapture.CapturedErrors.Empty();
if (bSuggestHandlerHelp && LastHandler)
{
WingManual::PrintHandlerHelp(*LastHandler);
}
if (!SuggestedManualSections.IsEmpty()) if (!SuggestedManualSections.IsEmpty())
{ {
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); WingOut::Stdout.Print(TEXT("Suggested manual sections: "));
WingManual::PrintManual(SuggestedManualSections, LastHandler, true); WingManual::PrintSectionNames(SuggestedManualSections);
} }
FString Result = WingOut::StdoutBuffer.ToString(); FString Result = WingOut::StdoutBuffer.ToString();
WingOut::StdoutBuffer.Reset(); WingOut::StdoutBuffer.Reset();
@@ -227,7 +232,7 @@ void UWingServer::TryCallHandler(const FString &Line)
if (!Found) if (!Found)
{ {
WingOut::Stdout.Printf(TEXT("Unknown command: %s"), *Command); WingOut::Stdout.Printf(TEXT("Unknown command: %s"), *Command);
UWingServer::SuggestManual(WingManual::Section::ImportantCommands); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, ImportantCommands));
return; return;
} }
LastHandler = Found; LastHandler = Found;
@@ -241,7 +246,7 @@ void UWingServer::TryCallHandler(const FString &Line)
TArray<FWingProperty> Props = FWingProperty::GetVisible(Handler); TArray<FWingProperty> Props = FWingProperty::GetVisible(Handler);
if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout)) if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout))
{ {
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
return; return;
} }

View File

@@ -430,7 +430,7 @@ void UWingTypes::PrintParseError(WingTokenizer& Tok, const TCHAR* Message, WingO
{ {
FString TypeText(Tok.GetRange(NAME_StartOfType, 1)); FString TypeText(Tok.GetRange(NAME_StartOfType, 1));
Errors.Printf(TEXT("ERROR parsing type '%s' — %s\n"), *TypeText, Message); Errors.Printf(TEXT("ERROR parsing type '%s' — %s\n"), *TypeText, Message);
UWingServer::SuggestManual(WingManual::Section::Types); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, ExpressingTypes));
} }
@@ -626,7 +626,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
if (IsNone && OutPinType.IsContainer()) if (IsNone && OutPinType.IsContainer())
{ {
Errors.Printf(TEXT("ERROR: 'None' is not allowed in an array/set/map\n")); Errors.Printf(TEXT("ERROR: 'None' is not allowed in an array/set/map\n"));
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
@@ -634,7 +634,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
if (!Require.AllowNone && IsNone) if (!Require.AllowNone && IsNone)
{ {
Errors.Printf(TEXT("ERROR: 'None' is not allowed here\n")); Errors.Printf(TEXT("ERROR: 'None' is not allowed here\n"));
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
@@ -644,7 +644,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{ {
FString Text(Tok.GetRange(NAME_StartOfType, 0)); FString Text(Tok.GetRange(NAME_StartOfType, 0));
Errors.Printf(TEXT("ERROR: Type '%s' is a container, not allowed here\n"), *Text); Errors.Printf(TEXT("ERROR: Type '%s' is a container, not allowed here\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
} }
@@ -655,7 +655,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{ {
Errors.Printf(TEXT("ERROR: Need a type which is an %s, got a %s instead.\n"), Errors.Printf(TEXT("ERROR: Need a type which is an %s, got a %s instead.\n"),
*Require.PinCategory.ToString(), *OutPinType.PinCategory.ToString()); *Require.PinCategory.ToString(), *OutPinType.PinCategory.ToString());
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
} }
@@ -668,13 +668,13 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{ {
FString Text(Tok.GetRange(NAME_StartOfType, 0)); FString Text(Tok.GetRange(NAME_StartOfType, 0));
Errors.Printf(TEXT("ERROR: '%s' is an interface, not a class\n"), *Text); Errors.Printf(TEXT("ERROR: '%s' is an interface, not a class\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
if (!IsChildOf(OutPinType, Require.IsChildOf)) if (!IsChildOf(OutPinType, Require.IsChildOf))
{ {
Errors.Printf(TEXT("ERROR: Type must derive from %s\n"), *WingUtils::FormatName(Require.IsChildOf)); Errors.Printf(TEXT("ERROR: Type must derive from %s\n"), *WingUtils::FormatName(Require.IsChildOf));
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
} }
@@ -685,7 +685,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{ {
FString Text(Tok.GetRange(NAME_StartOfType, 0)); FString Text(Tok.GetRange(NAME_StartOfType, 0));
Errors.Printf(TEXT("ERROR: Not a blueprint type: %s\n"), *Text); Errors.Printf(TEXT("ERROR: Not a blueprint type: %s\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
} }
@@ -696,7 +696,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{ {
FString Text(Tok.GetRange(NAME_StartOfType, 0)); FString Text(Tok.GetRange(NAME_StartOfType, 0));
Errors.Printf(TEXT("ERROR: Not a blueprintable type: %s\n"), *Text); Errors.Printf(TEXT("ERROR: Not a blueprintable type: %s\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp); UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false; OutPinType = FEdGraphPinType(); return false;
} }
} }

View File

@@ -60,7 +60,7 @@ FName WingUtils::CheckInternalizeID(const FString &ExternalID, WingOut Errors)
if (!Error.IsEmpty()) if (!Error.IsEmpty())
{ {
Errors.Printf(TEXT("%s\n"), *Error); Errors.Printf(TEXT("%s\n"), *Error);
UWingServer::SuggestManual(WingManual::Section::EscapeSequences); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, EscapeSequencesInFNames));
} }
return InternalID; return InternalID;
} }
@@ -77,7 +77,7 @@ FName WingUtils::CheckProposedName(const FString &ExternalID, const TSet<FName>
{ {
Errors.Printf(TEXT("ERROR: id %s would not be a readable id, may not create item with this name"), Errors.Printf(TEXT("ERROR: id %s would not be a readable id, may not create item with this name"),
*ExternalID); *ExternalID);
UWingServer::SuggestManual(WingManual::Section::EscapeSequences); UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, EscapeSequencesInFNames));
return FName(); return FName();
} }
if (InUse.Contains(InternalID)) if (InUse.Contains(InternalID))

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "WingManual.generated.h"
struct FWingHandlerConfig; struct FWingHandlerConfig;
enum class EWingHandlerKind; enum class EWingHandlerKind;
@@ -7,23 +8,37 @@ enum class EWingHandlerKind;
class WingManual class WingManual
{ {
public: public:
enum class Section
{
All,
HandlerHelp,
Paths,
Types,
VariableDeclarations,
EscapeSequences,
Whitespace,
MaterialEditing,
ImportantCommands,
};
static void PrintHandlerPrototype(const FWingHandlerConfig& Handler); static void PrintHandlerPrototype(const FWingHandlerConfig& Handler);
static void PrintHandlerArguments(const FWingHandlerConfig& Handler); static void PrintHandlerArguments(const FWingHandlerConfig& Handler);
static void PrintHandlerDescription(const FWingHandlerConfig& Handler); static void PrintHandlerDescription(const FWingHandlerConfig& Handler);
static void PrintHandlerHelp(const FWingHandlerConfig& Handler); static void PrintHandlerHelp(const FWingHandlerConfig& Handler);
static void PrintManual(TSet<Section> Sections, const FWingHandlerConfig* Handler, bool Abridged);
static void Commands(EWingHandlerKind Kind, const FString& Query, bool Verbose); static void Commands(EWingHandlerKind Kind, const FString& Query, bool Verbose);
static TSet<FName> GetSections();
static bool PrintSection(FName Section);
static void PrintSectionNames(const TSet<FName>& Sections);
};
UCLASS()
class UWingManualSections : public UObject
{
GENERATED_BODY()
public:
UFUNCTION()
static void FetcherPaths();
UFUNCTION()
static void ExpressingTypes();
UFUNCTION()
static void VariableDeclarations();
UFUNCTION()
static void EscapeSequencesInFNames();
UFUNCTION()
static void MaterialEditing();
UFUNCTION()
static void ImportantCommands();
}; };

View File

@@ -48,7 +48,10 @@ public:
static void AddTouchedObject(UObject* Obj) { GWingServer->Notifier.AddTouchedObject(Obj); } static void AddTouchedObject(UObject* Obj) { GWingServer->Notifier.AddTouchedObject(Obj); }
/** Suggest that a manual section be printed after the handler finishes. */ /** Suggest that a manual section be printed after the handler finishes. */
static void SuggestManual(WingManual::Section Section) { GWingServer->SuggestedManualSections.Add(Section); } static void SuggestManual(FName Section) { GWingServer->SuggestedManualSections.Add(Section); }
/** Suggest that handler help be printed after the handler finishes. */
static void SuggestHandlerHelp() { GWingServer->bSuggestHandlerHelp = true; }
/** Port the server is listening on. */ /** Port the server is listening on. */
int32 GetPort() const { return Port; } int32 GetPort() const { return Port; }
@@ -64,7 +67,8 @@ private:
UPROPERTY() UPROPERTY()
FWingNotifier Notifier; FWingNotifier Notifier;
FWingHandlerConfig* LastHandler; FWingHandlerConfig* LastHandler;
TSet<WingManual::Section> SuggestedManualSections; bool bSuggestHandlerHelp;
TSet<FName> SuggestedManualSections;
FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request
TArray<FWingHandlerConfig> WingHandlerRegistry; // sorted by Name TArray<FWingHandlerConfig> WingHandlerRegistry; // sorted by Name
void BuildWingHandlerRegistry(); void BuildWingHandlerRegistry();