Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Status of the `main` branch. Changes prior to the next official version change w
- Allow `query_project` tool to access read-only tools that are not enabled in the current configuration

* Language Servers:
- C/C++ (clangd): add Unreal Engine 5 fixture and tests verifying that reflection-macro
code (`UCLASS`, `UFUNCTION`, `UPROPERTY`, `GENERATED_BODY`) yields correct symbols,
references, definitions, and rename edits in hand-written sources (never in
UHT-generated files); add an Unreal Engine setup guide.
- `typescript_vts`: Add `initialization_options` setting in `ls_specific_settings.typescript_vts`.
The dict is forwarded to vtsls via `initializationOptions`, `workspace/didChangeConfiguration`,
and `workspace/configuration` pulls. Enables Yarn PnP setups with `typescript.tsdk` pointing
Expand Down
3 changes: 2 additions & 1 deletion docs/01-about/020_programming-languages.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ Some languages require additional installations or setup steps, as noted.
* **C/C++**
(by default, uses the clangd language server (language `cpp`) but we also support ccls (language `cpp_ccls`);
for best results, provide a `compile_commands.json` at the repository root;
see the [C/C++ Setup Guide](../03-special-guides/cpp_setup) for details.)
see the [C/C++ Setup Guide](../03-special-guides/cpp_setup) for details;
for Unreal Engine 5 projects, see the [Unreal Engine Setup Guide](../03-special-guides/unreal_engine_setup_guide_for_serena).)
* **Clojure**
* **Crystal**
(requires [Crystalline](https://github.com/elbywan/crystalline) language server to be installed and available on PATH;
Expand Down
5 changes: 5 additions & 0 deletions docs/03-special-guides/cpp_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,8 @@ the language server is restarted.
- Clangd official documentation: https://clangd.llvm.org/
- Clangd project setup: https://clangd.llvm.org/installation#project-setup
- CCLS repository: https://github.com/MaskRay/ccls

## Unreal Engine projects

For Unreal Engine 5 projects (reflection macros, UnrealBuildTool), see the
[Unreal Engine Setup Guide](unreal_engine_setup_guide_for_serena.md).
101 changes: 101 additions & 0 deletions docs/03-special-guides/unreal_engine_setup_guide_for_serena.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Unreal Engine Setup Guide

This guide explains how to prepare an Unreal Engine 5 C++ project so that Serena's
clangd-based C/C++ support can provide full code intelligence: symbol search,
cross-file references, and symbol-level editing in your hand-written sources.

UE game code uses a macro-based reflection layer (`UCLASS`, `UFUNCTION`, `UPROPERTY`,
`GENERATED_BODY`) and engine types (`TArray`, `TMap`). clangd handles all of this,
provided it receives the compiler flags for your project via a `compile_commands.json`
at the project root. Unreal's build system (UnrealBuildTool) does not produce this
file by default; this guide shows how to obtain it.

---
## Prerequisites

- An Unreal Engine 5 C++ project that has been **built at least once** (the build
generates the `*.generated.h` headers that your sources include).
- No additional language server: Serena downloads clangd automatically.
- clangd never compiles your code. The compilation database is only a list of flags.

---
## Getting a compilation database

Pick one of the following routes.

### Route 1: VSCode project files (no extra installs)

UnrealBuildTool's VSCode project generator emits per-project compile commands.
Run it from your engine installation (VSCode itself is not required):

<Engine>\Build\BatchFiles\Build.bat -projectfiles -project="<YourProject>.uproject" -game -VSCode

This produces `.vscode/compileCommands_<YourProject>.json` inside your project.
Copy or symlink it to the project root as `compile_commands.json`.

If you already use VSCode with UE, the file likely exists; the editor's
"Tools > Refresh Visual Studio Code Project" action maintains it.

### Route 2: UnrealBuildTool's clang database mode (requires LLVM installed)

<Engine>\Binaries\DotNET\UnrealBuildTool\UnrealBuildTool.exe -mode=GenerateClangDatabase -project="<YourProject>.uproject" <YourProject>Editor Win64 Development -OutputDir="<YourProject's directory>"

This emits clang-native commands (cleanest flags for clangd) but requires a Clang
toolchain installed on Windows.

---
## Recommended project configuration

Generated reflection code (`*.gen.cpp`, `*.generated.h`) legitimately references your
functions, so symbol results can include hits inside `Intermediate/`. Exclude UE's
build artifacts in your project's `.serena/project.yml`:

ignored_paths:
- "Intermediate"
- "Saved"
- "Binaries"
- "DerivedDataCache"

---
## Known behavior

- **`GENERATED_BODY()` and `__LINE__`:** the macro expands using its line number.
After editing lines above it, clangd may report stale-macro diagnostics until the
next build regenerates headers. Symbol operations keep working, since clangd is
designed to operate on code with errors.
- **First index:** large projects take a few minutes to index once; afterwards
results are incremental. The index cache is kept under `.serena/.cache` inside
the project.
- **New `UFUNCTION`/`UCLASS` declarations** need a build before their generated
headers exist.
- **Symbol searches on large projects:** prefer passing `relative_path` to
`find_symbol`. An unscoped search visits every translation unit, and UE
files are expensive to parse because each pulls in large engine headers.

---
## Troubleshooting

Extra flags are easiest to add via a `.clangd` file at the project root, e.g.:

CompileFlags:
Add: [-D_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH, -ferror-limit=200]

- **`STL1000: Unexpected compiler version` errors:** recent MSVC STL headers
assert a minimum Clang version that may be newer than Serena's bundled clangd.
Defining `_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH` (see above) silences the
check; clangd only parses, so the mismatch is harmless.
- **Truncated symbol trees / symbols missing below a certain line:** clangd
aborts a file's parse after ~20 errors by default, which discards everything
declared after that point. Raising the limit with `-ferror-limit=200` keeps
the symbol tree intact even when diagnostics are noisy (common right after
edits, before the next UE build regenerates headers).
- **Stale results after changing the compilation database:** clangd's index
shards in `.serena/.cache` were built with the old flags. Delete that cache
directory and let the project re-index.

---
## Verifying the setup

After activating the project in Serena, a symbol overview of any `UCLASS` header
should list the class with its `UFUNCTION` methods and `UPROPERTY` fields, and
references to a method should resolve to your `Source/` files only.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,4 @@ markers = [
skip = '.git*,*.svg,*.lock,*.min.*,*test_memories_manager.py'
check-hidden = true
ignore-regex = '\.\w+'
ignore-words-list = 'paket,EDN,als'
ignore-words-list = 'paket,EDN,als,ue'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*===========================================================================
Generated code exported from UnrealHeaderTool.
DO NOT modify this manually! Edit the corresponding .h files instead!
===========================================================================*/
// Test stub: trimmed to what the fixture needs.
// DECLARE_FUNCTION(execOnAbilityInput)
#pragma once

class AAbilityActor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*===========================================================================
Generated code exported from UnrealHeaderTool.
DO NOT modify this manually! Edit the corresponding .h files instead!
===========================================================================*/
// Test stub: repeats the hand-written identifiers (as real thunks do) so a
// text search would surface this file; symbol-level tools must not.
//
// void UAbilityComponent::execTriggerAbility(...) { TriggerAbility(AbilityName); }
// void UAbilityComponent::execGetRemainingCooldown(...) { GetRemainingCooldown(AbilityName); }
// void AAbilityActor::execOnAbilityInput(...) { OnAbilityInput(AbilityName); }

namespace UnrealReflectionStub
{
const char* GeneratedSymbols[] = {
"UAbilityComponent",
"TriggerAbility",
"GetRemainingCooldown",
"AAbilityActor",
"OnAbilityInput",
"FAbilityInfo",
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*===========================================================================
Generated code exported from UnrealHeaderTool.
DO NOT modify this manually! Edit the corresponding .h files instead!
===========================================================================*/
// Test stub: trimmed to what the fixture needs.
// DECLARE_FUNCTION(execTriggerAbility)
// DECLARE_FUNCTION(execGetRemainingCooldown)
#pragma once

class UAbilityComponent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*===========================================================================
Generated code exported from UnrealHeaderTool.
DO NOT modify this manually! Edit the corresponding .h files instead!
===========================================================================*/
// Test stub: trimmed to what the fixture needs.
#pragma once

struct FAbilityInfo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*===========================================================================
Generated code exported from UnrealHeaderTool.
DO NOT modify this manually! Edit the corresponding .h files instead!
===========================================================================*/
// Test stub: trimmed to what the fixture needs.
// DECLARE_FUNCTION(execReceiveDamage)
#pragma once

class UDamageable;
class IDamageable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*===========================================================================
Generated code exported from UnrealHeaderTool.
DO NOT modify this manually! Edit the corresponding .h files instead!
===========================================================================*/
// Test stub: trimmed to what the fixture needs.
// DECLARE_FUNCTION(execHeal)
// DECLARE_FUNCTION(execReceiveDamage)
#pragma once

class AGameCharacter;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include "AbilityActor.h"

void AAbilityActor::BeginPlay()
{
AActor::BeginPlay();
if (AbilityComponent)
{
AbilityComponent->TriggerAbility(FName("Dash"));
}
}

void AAbilityActor::OnAbilityInput(const FName& AbilityName)
{
if (AbilityComponent)
{
AbilityComponent->TriggerAbility(AbilityName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "AbilityComponent.h"
#include "AbilityActor.generated.h"

UCLASS(Blueprintable)
class TESTGAME_API AAbilityActor : public AActor
{
GENERATED_BODY()

public:
virtual void BeginPlay() override;

UFUNCTION(BlueprintCallable, Category = "Input")
void OnAbilityInput(const FName& AbilityName);

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Components")
TObjectPtr<UAbilityComponent> AbilityComponent = nullptr;

UPROPERTY(EditDefaultsOnly, Category = "Abilities")
TSubclassOf<UAbilityComponent> ComponentClass;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "AbilityComponent.h"

void UAbilityComponent::TriggerAbility(const FName& AbilityName)
{
if (!ActiveCooldowns.Contains(AbilityName))
{
ActiveCooldowns.Add(AbilityName, 1.0f);
}
State = EAbilityState::Active;
OnAbilityTriggered.Broadcast(AbilityName);
}

float UAbilityComponent::GetRemainingCooldown(const FName& AbilityName) const
{
return 0.0f;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "AbilityTypes.h"
#include "AbilityComponent.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAbilityTriggered, const FName&, AbilityName);
DECLARE_MULTICAST_DELEGATE_OneParam(FOnCooldownExpired, const FName&);

UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
class TESTGAME_API UAbilityComponent : public UActorComponent
{
GENERATED_BODY()

public:
UFUNCTION(BlueprintCallable, Category = "Abilities")
void TriggerAbility(const FName& AbilityName);

UFUNCTION(BlueprintPure, Category = "Abilities")
float GetRemainingCooldown(const FName& AbilityName) const;

UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Abilities")
TArray<FAbilityInfo> Abilities;

UPROPERTY(VisibleAnywhere, Category = "Abilities")
TMap<FName, float> ActiveCooldowns;

UPROPERTY(BlueprintAssignable, Category = "Abilities")
FOnAbilityTriggered OnAbilityTriggered;

UPROPERTY(EditAnywhere, Category = "Abilities")
EAbilityState State = EAbilityState::Idle;

UPROPERTY(EditAnywhere, Category = "Abilities")
TSet<FName> UnlockedAbilities;

// Non-dynamic delegates are not reflected and cannot be UPROPERTYs.
FOnCooldownExpired OnCooldownExpired;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once

#include "CoreMinimal.h"
#include "AbilityTypes.generated.h"

UENUM(BlueprintType)
enum class EAbilityState : uint8
{
Idle,
Active UMETA(DisplayName = "Active (in use)"),
Cooldown,
};

USTRUCT(BlueprintType)
struct TESTGAME_API FAbilityInfo
{
GENERATED_BODY()

UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName AbilityName;

UPROPERTY(EditAnywhere, BlueprintReadWrite)
float CooldownSeconds = 1.0f;
};
20 changes: 20 additions & 0 deletions test/resources/repos/cpp/ue_test_repo/Source/TestGame/Damageable.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include "CoreMinimal.h"
#include "UObject/Interface.h"
#include "Damageable.generated.h"

UINTERFACE(MinimalAPI, Blueprintable)
class UDamageable : public UInterface
{
GENERATED_BODY()
};

class TESTGAME_API IDamageable
{
GENERATED_BODY()

public:
UFUNCTION(BlueprintCallable, BlueprintNativeEvent, Category = "Damage")
virtual void ReceiveDamage(float Amount) = 0;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include "GameCharacter.h"
#include "TestGameLog.h"

AGameCharacter::AGameCharacter()
{
Abilities = CreateDefaultSubobject<UAbilityComponent>(FName(TEXT("Abilities")));
}

void AGameCharacter::ReceiveDamage(float Amount)
{
check(Amount >= 0.0f);
Health -= Amount;
LastDamageAmount = Amount;
UE_LOG(LogTestGame, Warning, TEXT("Received %f damage"), Amount);

if (AActor* Target = CurrentTarget.Get())
{
if (AGameCharacter* OtherCharacter = Cast<AGameCharacter>(Target))
{
OtherCharacter->Heal(Amount);
}
}
}

void AGameCharacter::Heal(float& Amount)
{
Health += Amount;
Amount = 0.0f;
}
Loading
Loading