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()
public:
UPROPERTY(EditAnywhere, meta=(Optional, Description="If specified, print only this section of the manual"))
FString Section;
virtual void Register() override
{
UWingServer::AddHandler(this,
@@ -19,6 +22,23 @@ public:
}
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);
else
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();
}
@@ -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);
else
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();
return *this;
}
@@ -75,8 +75,8 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
if (Path.Contains(TEXT(" ")))
{
Errors.Printf(TEXT("ERROR: Paths may not contain whitespace."));
UWingServer::SuggestManual(WingManual::Section::Paths);
UWingServer::SuggestManual(WingManual::Section::EscapeSequences);
UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, EscapeSequencesInFNames));
return SetError();
}
TArray<FString> Segments;
@@ -84,7 +84,7 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
if (Segments.Num() == 0)
{
Errors.Print(TEXT("ERROR: Empty path\n"));
UWingServer::SuggestManual(WingManual::Section::Paths);
UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, FetcherPaths));
return SetError();
}
@@ -105,7 +105,7 @@ WingFetcher& WingFetcher::Walk(const FString& Path)
if (!Func)
{
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();
}
(this->*Func)(Value);
@@ -122,7 +122,7 @@ WingFetcher& WingFetcher::Asset(const FString& PackagePath)
if (!PackagePath.StartsWith(TEXT("/")))
{
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();
}
@@ -182,7 +182,7 @@ WingFetcher& WingFetcher::Graph(const FString& Value)
if (!Value.IsEmpty())
{
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();
}
WingUtils::EnsureMaterialGraph(Mat);

View File

@@ -58,42 +58,14 @@ void WingManual::PrintHandlerHelp(const FWingHandlerConfig& Handler)
WingOut::Stdout.Print(TEXT("\n"));
}
void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* Handler, bool Abridged)
{
if (Sections.IsEmpty()) return;
const bool bPrintAll = Sections.Contains(Section::All);
if (Abridged)
{
WingOut::Stdout.Printf(TEXT("\n--- AUTOMATIC DOCUMENTATION ---\n"));
}
if (Handler && (Sections.Contains(Section::HandlerHelp) || bPrintAll))
{
PrintHandlerHelp(*Handler);
}
if (Sections.Contains(Section::Paths) || bPrintAll)
{
if (Abridged)
void UWingManualSections::FetcherPaths()
{
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 FETCHER PATHS:"
"\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 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"
@@ -107,9 +79,10 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\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 sanitized identifiers. See the section"
"\n on identifier sanitization below for more information."
"\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:"
@@ -118,28 +91,14 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\n"
));
}
}
if (Sections.Contains(Section::Types) || bPrintAll)
{
if (Abridged)
void UWingManualSections::ExpressingTypes()
{
WingOut::Stdout.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 EXPRESSING TYPES:"
"\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 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,"
@@ -148,24 +107,13 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\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 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 (Sections.Contains(Section::VariableDeclarations) || bPrintAll)
{
if (Abridged)
{
WingOut::Stdout.Print(TEXT(
"\n VARIABLE DECLARATIONS: example variable declarations:"
"\n Array<Actor> Actors"
"\n Float F (InstanceEditable)"
"\n String S = This is the default value"
));
}
else
void UWingManualSections::VariableDeclarations()
{
WingOut::Stdout.Print(TEXT(
"\n VARIABLE DECLARATIONS:"
@@ -179,7 +127,7 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\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 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"
@@ -187,40 +135,12 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\n"
));
}
}
if (Sections.Contains(Section::EscapeSequences) || bPrintAll)
{
if (Abridged)
void UWingManualSections::EscapeSequencesInFNames()
{
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 ESCAPE SEQUENCES IN FNAMES:"
"\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."
@@ -231,32 +151,8 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\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
void UWingManualSections::MaterialEditing()
{
WingOut::Stdout.Print(TEXT(
"\n MATERIAL EDITING:"
@@ -267,15 +163,18 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\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"
));
}
}
if (Sections.Contains(Section::ImportantCommands) || bPrintAll)
void UWingManualSections::ImportantCommands()
{
WingOut::Stdout.Print(TEXT(
"\n COMMANDS YOU SHOULD KNOW ABOUT AND REMEMBER:"
"\n Documentation_Manual: this explanation"
"\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"
@@ -283,16 +182,44 @@ void WingManual::PrintManual(TSet<Section> Sections, const FWingHandlerConfig* H
"\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 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"
));
}
if (Abridged)
TSet<FName> WingManual::GetSections()
{
WingOut::Stdout.Printf(TEXT("\nUse command 'Documentation_Manual' to see the full manual.\n"));
TSet<FName> Result;
for (TFieldIterator<UFunction> It(UWingManualSections::StaticClass(), EFieldIterationFlags::None); It; ++It)
{
Result.Add(It->GetFName());
}
return Result;
}
void WingManual::PrintSectionNames(const TSet<FName>& Sections)
{
if (Sections.IsEmpty()) return;
bool bFirst = true;
for (const FName& Section : Sections)
{
if (!bFirst) WingOut::Stdout.Print(TEXT(", "));
bFirst = false;
WingOut::Stdout.Printf(TEXT("%s"), *Section.ToString());
}
WingOut::Stdout.Print(TEXT("\n"));
}
bool WingManual::PrintSection(FName Section)
{
UFunction* Func = UWingManualSections::StaticClass()->FindFunctionByName(Section);
if (!Func) return false;
UWingManualSections::StaticClass()->GetDefaultObject()->ProcessEvent(Func, nullptr);
return true;
}
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;
WingOut::StdoutBuffer.Reset();
SuggestedManualSections.Empty();
bSuggestHandlerHelp = false;
LastHandler = nullptr;
TryCallHandler(Line);
@@ -186,10 +187,14 @@ FString UWingServer::HandleRequest(const FString& Line)
WingOut::Stdout.Printf(TEXT("UE_LOG: %s\n"), *Msg);
}
LogCapture.CapturedErrors.Empty();
if (bSuggestHandlerHelp && LastHandler)
{
WingManual::PrintHandlerHelp(*LastHandler);
}
if (!SuggestedManualSections.IsEmpty())
{
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
WingManual::PrintManual(SuggestedManualSections, LastHandler, true);
WingOut::Stdout.Print(TEXT("Suggested manual sections: "));
WingManual::PrintSectionNames(SuggestedManualSections);
}
FString Result = WingOut::StdoutBuffer.ToString();
WingOut::StdoutBuffer.Reset();
@@ -227,7 +232,7 @@ void UWingServer::TryCallHandler(const FString &Line)
if (!Found)
{
WingOut::Stdout.Printf(TEXT("Unknown command: %s"), *Command);
UWingServer::SuggestManual(WingManual::Section::ImportantCommands);
UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, ImportantCommands));
return;
}
LastHandler = Found;
@@ -241,7 +246,7 @@ void UWingServer::TryCallHandler(const FString &Line)
TArray<FWingProperty> Props = FWingProperty::GetVisible(Handler);
if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout))
{
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
UWingServer::SuggestHandlerHelp();
return;
}

View File

@@ -430,7 +430,7 @@ void UWingTypes::PrintParseError(WingTokenizer& Tok, const TCHAR* Message, WingO
{
FString TypeText(Tok.GetRange(NAME_StartOfType, 1));
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())
{
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;
}
@@ -634,7 +634,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
if (!Require.AllowNone && IsNone)
{
Errors.Printf(TEXT("ERROR: 'None' is not allowed here\n"));
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false;
}
@@ -644,7 +644,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{
FString Text(Tok.GetRange(NAME_StartOfType, 0));
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;
}
}
@@ -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"),
*Require.PinCategory.ToString(), *OutPinType.PinCategory.ToString());
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false;
}
}
@@ -668,13 +668,13 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{
FString Text(Tok.GetRange(NAME_StartOfType, 0));
Errors.Printf(TEXT("ERROR: '%s' is an interface, not a class\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false;
}
if (!IsChildOf(OutPinType, 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;
}
}
@@ -685,7 +685,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{
FString Text(Tok.GetRange(NAME_StartOfType, 0));
Errors.Printf(TEXT("ERROR: Not a blueprint type: %s\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false;
}
}
@@ -696,7 +696,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
{
FString Text(Tok.GetRange(NAME_StartOfType, 0));
Errors.Printf(TEXT("ERROR: Not a blueprintable type: %s\n"), *Text);
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
UWingServer::SuggestHandlerHelp();
OutPinType = FEdGraphPinType(); return false;
}
}

View File

@@ -60,7 +60,7 @@ FName WingUtils::CheckInternalizeID(const FString &ExternalID, WingOut Errors)
if (!Error.IsEmpty())
{
Errors.Printf(TEXT("%s\n"), *Error);
UWingServer::SuggestManual(WingManual::Section::EscapeSequences);
UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, EscapeSequencesInFNames));
}
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"),
*ExternalID);
UWingServer::SuggestManual(WingManual::Section::EscapeSequences);
UWingServer::SuggestManual(GET_FUNCTION_NAME_CHECKED(UWingManualSections, EscapeSequencesInFNames));
return FName();
}
if (InUse.Contains(InternalID))

View File

@@ -1,5 +1,6 @@
#pragma once
#include "CoreMinimal.h"
#include "WingManual.generated.h"
struct FWingHandlerConfig;
enum class EWingHandlerKind;
@@ -7,23 +8,37 @@ enum class EWingHandlerKind;
class WingManual
{
public:
enum class Section
{
All,
HandlerHelp,
Paths,
Types,
VariableDeclarations,
EscapeSequences,
Whitespace,
MaterialEditing,
ImportantCommands,
};
static void PrintHandlerPrototype(const FWingHandlerConfig& Handler);
static void PrintHandlerArguments(const FWingHandlerConfig& Handler);
static void PrintHandlerDescription(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 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); }
/** 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. */
int32 GetPort() const { return Port; }
@@ -64,7 +67,8 @@ private:
UPROPERTY()
FWingNotifier Notifier;
FWingHandlerConfig* LastHandler;
TSet<WingManual::Section> SuggestedManualSections;
bool bSuggestHandlerHelp;
TSet<FName> SuggestedManualSections;
FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request
TArray<FWingHandlerConfig> WingHandlerRegistry; // sorted by Name
void BuildWingHandlerRegistry();