Skip to content
Merged
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 CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,10 @@ For that, you can use the `jd:project_name[:module_name][:class_or_member_refere
[`repeat(long, TimeUnit)`](jd:velocity:com.velocitypowered.api.scheduler.Scheduler$TaskBuilder#repeat(long,java.util.concurrent.TimeUnit))
[java.base's List](jd:java:java.util.List)
[java.sql's Connection](jd:java:java.sql:java.sql.Connection)

We don't even need to specify the link text, it can be generated automatically:
[](jd:paper:org.bukkit.event.Event) looks like `Event`
[](jd:velocity:com.velocitypowered.api.scheduler.Scheduler$TaskBuilder#repeat(long,java.util.concurrent.TimeUnit)) looks like `Scheduler.TaskBuilder#repeat(long, TimeUnit)`
```

## Referencing a build system dependency
Expand Down
34 changes: 17 additions & 17 deletions src/content/docs/paper/dev/api/event-api/chat-event.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@ slug: paper/dev/chat-events
---

The chat event has evolved a few times over the years.
This guide will explain how to properly use the new [`AsyncChatEvent`](jd:paper:io.papermc.paper.event.player.AsyncChatEvent)
and its [`ChatRenderer`](jd:paper:io.papermc.paper.chat.ChatRenderer).
The [`AsyncChatEvent`](jd:paper:io.papermc.paper.event.player.AsyncChatEvent)
is an improved version of the old [`AsyncPlayerChatEvent`](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent)
This guide will explain how to properly use the new [](jd:paper:io.papermc.paper.event.player.AsyncChatEvent)
and its [](jd:paper:io.papermc.paper.chat.ChatRenderer).
The [](jd:paper:io.papermc.paper.event.player.AsyncChatEvent)
is an improved version of the old [](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent)
that allows you to render chat messages individually for each player.

:::note[`AsyncChatEvent` vs `ChatEvent`]

The key difference between [`AsyncChatEvent`](jd:paper:io.papermc.paper.event.player.AsyncChatEvent)
and [`ChatEvent`](jd:paper:io.papermc.paper.event.player.ChatEvent) is that
[`AsyncChatEvent`](jd:paper:io.papermc.paper.event.player.AsyncChatEvent) is fired asynchronously.
The key difference between [](jd:paper:io.papermc.paper.event.player.AsyncChatEvent)
and [](jd:paper:io.papermc.paper.event.player.ChatEvent) is that
[](jd:paper:io.papermc.paper.event.player.AsyncChatEvent) is fired asynchronously.

This means that it does not block the main thread and sends the chat message when the listener has completed.
Be aware that using the Bukkit API in an asynchronous context (i.e. the event handler) is unsafe and exceptions may be thrown.
If you need to use the Bukkit API, you can use [`ChatEvent`](jd:paper:io.papermc.paper.event.player.ChatEvent).
If you need to use the Bukkit API, you can use [](jd:paper:io.papermc.paper.event.player.ChatEvent).
However, we recommend using [`BukkitScheduler`](/paper/dev/scheduler).

:::
Expand All @@ -28,10 +28,10 @@ However, we recommend using [`BukkitScheduler`](/paper/dev/scheduler).

Before we can start using the new chat event, we need to understand how the new renderer works.
The renderer is Paper's way of allowing plugins to modify the chat message before it is sent to the player.
This is done by using the [`ChatRenderer`](jd:paper:io.papermc.paper.chat.ChatRenderer) interface with its
[`render`](jd:paper:io.papermc.paper.chat.ChatRenderer#render(org.bukkit.entity.Player,net.kyori.adventure.text.Component,net.kyori.adventure.text.Component,net.kyori.adventure.audience.Audience))
method. Previously, this was done by using the [`AsyncPlayerChatEvent`](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent)
with its [`setFormat`](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent#setFormat(java.lang.String)) method.
This is done by using the [](jd:paper:io.papermc.paper.chat.ChatRenderer) interface with its
[](jd:paper:io.papermc.paper.chat.ChatRenderer#render(org.bukkit.entity.Player,net.kyori.adventure.text.Component,net.kyori.adventure.text.Component,net.kyori.adventure.audience.Audience))
method. Previously, this was done by using the [](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent)
with its [](jd:paper:org.bukkit.event.player.AsyncPlayerChatEvent#setFormat(java.lang.String)) method.

```java title="ChatRenderer#render"
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
Expand All @@ -48,27 +48,27 @@ public Component render(Player source, Component sourceDisplayName, Component me
:::tip[`ChatRenderer.ViewerUnaware`]

If your renderer does not need to know about the viewer, you can use the
[`ChatRenderer.ViewerUnaware`](jd:paper:io.papermc.paper.chat.ChatRenderer$ViewerUnaware)
interface instead of the [`ChatRenderer`](jd:paper:io.papermc.paper.chat.ChatRenderer) interface.
[](jd:paper:io.papermc.paper.chat.ChatRenderer$ViewerUnaware)
interface instead of the [](jd:paper:io.papermc.paper.chat.ChatRenderer) interface.
This will benefit performance as the message will only be rendered once instead of each individual player.

:::

## Using the renderer

There are two ways to use the renderer.
1. Implementing the [`ChatRenderer`](jd:paper:io.papermc.paper.chat.ChatRenderer) interface in a class.
1. Implementing the [](jd:paper:io.papermc.paper.chat.ChatRenderer) interface in a class.
2. Using a lambda expression.

Depending on the complexity of your renderer, you may want to use one or the other.

### Implementing the `ChatRenderer` interface

The first way of using the renderer is by implementing the [`ChatRenderer`](jd:paper:io.papermc.paper.chat.ChatRenderer)
The first way of using the renderer is by implementing the [](jd:paper:io.papermc.paper.chat.ChatRenderer)
interface in a class. In this example, we will be using our `ChatListener` class.

Next, we need to tell the event to use the renderer by using the
[`renderer`](jd:paper:io.papermc.paper.event.player.AbstractChatEvent#renderer()) method.
[](jd:paper:io.papermc.paper.event.player.AbstractChatEvent#renderer()) method.

```java title="ChatListener.java"
public class ChatListener implements Listener, ChatRenderer { // Implement the ChatRenderer and Listener interface
Expand Down
39 changes: 34 additions & 5 deletions src/utils/remark/javadoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,27 @@ const asUrl = (name: string): string => {
return `${name0}.html` + (hash ? `#${hash}` : "");
};

const asRef = (name: string): string => {
let [name0, hash] = name.split("#", 2);
name0 = name0.replaceAll(".", "/");

const lastSlash = name0.lastIndexOf("/");
if (lastSlash !== -1) {
// remove package
name0 = name0.substring(lastSlash + 1);
}
name0 = name0.replaceAll("$", ".");

const parenIndex = hash?.indexOf("(");
if (hash && parenIndex !== -1) {
// method parameters
const params = hash.substring(parenIndex + 1, hash.length - 1).split(",");
hash = `${hash.substring(0, parenIndex)}(${params.map((p) => p.substring(p.lastIndexOf(".") + 1)).join(", ")})`;
}

return name0 + (hash ? `#${hash}` : "");
};

const error = (err: any): never => {
if (process.env.NODE_ENV === "production") {
console.error(err);
Expand All @@ -35,14 +56,19 @@ const error = (err: any): never => {
}
};

const parse = async (url: string, { targets }: Options): Promise<string | null> => {
interface ParseResult {
url?: string;
ref?: string;
}

const parse = async (url: string, { targets }: Options): Promise<ParseResult> => {
const match = /^jd:(.+?)(?::(.+?))?(?::(.+?))?$/.exec(url);
if (!match) {
if (url.startsWith("jd:")) {
error(new Error(`Failed to parse Javadoc link "${url}"`));
}

return null; // not a Javadoc link
return {}; // not a Javadoc link
}

const target = targets[match[1]];
Expand All @@ -54,7 +80,7 @@ const parse = async (url: string, { targets }: Options): Promise<string | null>

const name = match[3] ?? match[2];
if (!name) {
return targetUrl;
return { url: targetUrl };
}

const module = match[3] ? match[2] : typeof target !== "string" ? target.module : undefined;
Expand All @@ -72,16 +98,19 @@ const parse = async (url: string, { targets }: Options): Promise<string | null>
}
}

return parsed;
return { url: parsed, ref: asRef(name) };
};

const plugin: RemarkPlugin = (options: Options) => {
return async (tree) => {
const promises: Promise<void>[] = [];
visit(tree, "link", (node) => {
promises.push(
parse(node.url, options).then((url) => {
parse(node.url, options).then(({ url, ref }) => {
node.url = url ?? node.url;
if (ref && node.children.length === 0) {
node.children.push({ type: "inlineCode", value: ref });
}
})
);
});
Expand Down