Skip to content

Fix formatting of code longer than Discord's message limit#549

Open
Neil-Tomar wants to merge 4 commits into
Java-Discord:mainfrom
Neil-Tomar:fix/format-code-long-messages
Open

Fix formatting of code longer than Discord's message limit#549
Neil-Tomar wants to merge 4 commits into
Java-Discord:mainfrom
Neil-Tomar:fix/format-code-long-messages

Conversation

@Neil-Tomar

Copy link
Copy Markdown

Description of the Changes

The "Format Code" / "Format and Indent Code" message commands and the /format-code slash command failed when the target message was longer than Discord's 2000-character limit: the whole snippet was wrapped in a single code block and sent as one message, which Discord rejects — so the command silently did nothing.

This PR splits the formatted output into chunks that each stay under the limit:

  • New Code class splits content into ≤1980-character chunks (breaking on newlines where possible) and renders each as a code block.
  • The commands acknowledge the interaction by replying with the full, unsplit code as an attached file, then post the chunks in order.
  • New Language enum + Language.fromString(...) maps the slash command's format option to a code-fence tag.

A follow-up PR will add the language-selection dropdown.

@Neil-Tomar Neil-Tomar requested a review from a team as a code owner June 20, 2026 15:17

@danthe1st danthe1st left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Congratulations on writing your first pull request. I commented on a few things I noticed. Please don't unnecessarily duplicate code.

Comment thread src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java Outdated
}
channel.sendMessage(messages.get(index))
.setAllowedMentions(List.of())
.setComponents(FormatCodeCommand.buildActionRow(event.getTarget(), event.getUser().getIdLong()))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The action row including the buttons for deleting and the URL for jumping back are no longer present because you removed them here. Please add them to all messages.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a reason for that. The buttons only affect the message they are attached to, so adding them to each message would only allow deletion of that specific message.

Adding buttons to every message would also introduce a visual break inside the code block, which could hurt readability, as shown in the screenshots.

If you'd prefer the buttons to appear only on the last message while still affecting the entire code block, I can look into implementing that. I'm not familiar with that approach yet, but I'm happy to investigate it.

image

Here is with buttons:

image

as you can see it's hard to read through indentation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is a single message, both buttons should definitely be there.
If it consists of multiple messages, at least the View Original button should be present in the last message. There could also be a delete button that deletes all codeblock messages on the last message (and the file upload message should also have both buttons).

this.content = content;
}

public String getContent() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think getContent() and setLanguage are unused. If so, please remove them.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getContent() is still present.

@Neil-Tomar Neil-Tomar Jun 24, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getContent() in now being used in FormatCodeDispatcher at line 34 and 44.

@danthe1st

Copy link
Copy Markdown
Member

Fixes #537

@danthe1st danthe1st linked an issue Jun 20, 2026 that may be closed by this pull request
@Neil-Tomar Neil-Tomar requested a review from danthe1st June 23, 2026 15:30
…nto fix/format-code-long-messages

# Conflicts:
#	src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Code.java
#	src/main/java/net/discordjug/javabot/systems/user_commands/format_code/FormatCodeDispatcher.java
#	src/main/java/net/discordjug/javabot/systems/user_commands/format_code/Language.java

@danthe1st danthe1st left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are mostly minor comments. Is there any good reason why users would want a file response in addition to the codeblocks?

*/
private static final int MAX_SIZE = 1980;

private Language language;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you plan on needing to mutate Language in the follow-up PR you planned? If not, you could make this final or make Code a record.

* downloadable file, then posts it as one or more ordered code-block messages that each respect
* Discord's 2000-character limit.
*/
public class FormatCodeDispatcher {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is only used internally and could be package private.

* @return an action row containing the delete and "View Original" buttons
*/
@Contract("_ -> new")
static @NotNull ActionRow buildActionRow(@NotNull Message target, long requesterId) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this could be moved to FormatCodeDispatcher as that's the place for common code but it's fine as-is.


MessageChannel channel = target.getChannel();

event.replyFiles(file)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason to reply with a file vs just using event.reply... on the first message and skipping the file?

At least if it's only a single chunk, there shouldn't be any file (but I think it's also better without a file with multiple messages).

Make sure to update the Javadoc accordingly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried dropping the file and replying with the first chunk as suggested, but it hits two problems:

Sending the chunks as plain messages with no reply leaves the interaction unacknowledged → "This application did not respond."
Replying with the first chunk does acknowledge it, but the reply renders as its own attributed block, separate from the follow-up messages, so the code gets split visually mid-block.
image

Replying with the file avoids both: it acknowledges the interaction, and since the reply is a file (not a chunk) the code-block messages stay uniform with no break — plus it's a clean, copyable full version that reads nicely on desktop. The file seemed like the most useful thing to put there.

So, my preference would actually be to keep the file for both the single- and multi-chunk cases — it gives a consistent experience either way. But if you'd rather it was gone, I'm happy to remove it for the single-chunk case (just reply with the one block directly), or if you have a different approach in mind, I'll gladly go with that.

Responses.error(event.getHook(), "There is no code to format in that message.").queue();
return;
}
// Currently we always format as Java. A language dropdown will be added in the future.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That comment is incorrect. The Code can have a language with /format-code.

.setAllowedMentions(List.of());

if (index == messages.size() - 1) {
action.setComponents(buildActionRow(target, event.getUser().getIdLong()));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add the standard delete button if there is only a single message.

if (index >= messages.size()) {
return;
}
var action = channel.sendMessage(messages.get(index))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you remove the file upload, you can do something like this:

String content = messages.get(index);
MessageRequest<?> action = index == 0 ? event.reply(content) : channel.sendMessage(content);
action.setAllowedMentions(List.of());

* syntax highlighting inside code blocks.
*/
public enum Language {
/**

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you don't need Javadoc comments for the enum constants here (but please check using mvn verify).

/**
* Fallback used when the language is unrecognised; renders as plain text.
*/
UNKNOWN("Unknown", "txt");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are removing the replyFiles, I think you can change the "txt" to "".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Exception formatting code >2k characters

2 participants