Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Commit

Permalink
Merge branch 'highlighter'
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/main/java/io/github/homchom/recode/mod/features/commands/schem/loaders/MSpongeSchematicReader.java
  • Loading branch information
homchom committed Dec 14, 2023
2 parents 40c7ce2 + bb0d338 commit bb19cf7
Show file tree
Hide file tree
Showing 61 changed files with 1,224 additions and 670 deletions.
6 changes: 3 additions & 3 deletions src/main/java/io/github/homchom/recode/RecodeDispatcher.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.github.homchom.recode

import io.github.homchom.recode.ui.sendSystemToast
import io.github.homchom.recode.ui.translateText
import io.github.homchom.recode.ui.text.translatedText
import kotlinx.coroutines.*
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.Executor
Expand Down Expand Up @@ -42,8 +42,8 @@ object RecodeDispatcher : CoroutineContext {

private val delegate = dispatcher + CoroutineExceptionHandler { _, exception ->
mc.sendSystemToast(
translateText("recode.uncaught_exception.toast.title"),
translateText("recode.uncaught_exception.toast")
translatedText("recode.uncaught_exception.toast.title"),
translatedText("recode.uncaught_exception.toast")
)
runOnMinecraftThread {
val thread = Thread.currentThread()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import io.github.homchom.recode.event.Listenable
import io.github.homchom.recode.event.Requester
import io.github.homchom.recode.event.createEvent
import io.github.homchom.recode.ui.sendSystemToast
import io.github.homchom.recode.ui.translateText
import io.github.homchom.recode.ui.text.translatedText
import io.github.homchom.recode.util.computeNullable
import io.github.homchom.recode.util.coroutines.cancelAndLog
import kotlinx.coroutines.*
Expand Down Expand Up @@ -200,8 +200,8 @@ private class TrialRequester<T, R : Any>(
response
} catch (timeout: TimeoutCancellationException) {
mc.sendSystemToast(
translateText("multiplayer.recode.request_timeout.toast.title"),
translateText("multiplayer.recode.request_timeout.toast")
translatedText("multiplayer.recode.request_timeout.toast.title"),
translatedText("multiplayer.recode.request_timeout.toast")
)
logError("${debugString(input, hidden)} timed out after $timeoutDuration")
throw timeout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ class TrialScope @DelicateCoroutinesApi constructor(
}

/**
* Returns a non-null [TestResult.value] or fails the trial.
* @return a non-null [TestResult.value] or fails the trial.
*/
operator fun <T : Any> TestResult<T>.unaryPlus() = value ?: fail()

Expand All @@ -194,12 +194,12 @@ class TrialScope @DelicateCoroutinesApi constructor(
fun fail(): Nothing = nullableScope.fail()

/**
* Returns an instant [TrialResult] with [value]. Use this when a trial does not end asynchronously.
* @return an instant [TrialResult] with [value]. Use this when a trial does not end asynchronously.
*/
fun <R : Any> instant(value: R?) = TrialResult(value)

/**
* Returns the asynchronous [TrialResult] of [block] ran in its own [TrialScope].
* @return the asynchronous [TrialResult] of [block] ran in its own [TrialScope].
*/
fun <R : Any> suspending(block: suspend TrialScope.() -> R?) =
TrialResult(block, coroutineScope, hidden)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ package io.github.homchom.recode.feature.social

import io.github.homchom.recode.MOD_NAME
import io.github.homchom.recode.render.ColorPalette
import io.github.homchom.recode.ui.text
import io.github.homchom.recode.ui.text.literalText
import io.github.homchom.recode.ui.text.style
import io.github.homchom.recode.ui.text.toVanilla
import io.github.homchom.recode.ui.text.translatedText
import io.github.homchom.recode.util.regex.regex
import net.minecraft.client.GuiMessageTag

Expand All @@ -18,9 +21,11 @@ private val stackRegex = regex {
fun stackedMessageTag(amount: Int) = GuiMessageTag(
ColorPalette.AQUA.hex,
GuiMessageTag.Icon.CHAT_MODIFIED,
text {
color(aqua) { translate("chat.tag.recode.stacked", amount) }
},
translatedText(
"chat.tag.recode.stacked",
style().aqua(),
arrayOf(literalText(amount))
).toVanilla(),
"$stackTagPrefix$amount"
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ class SideChat(private val mc: Minecraft) : ChatComponent(mc) {
private fun tailWidth(scale: Double) = (12 * scale).toInt()
}

/**
* A duck interface applied to [net.minecraft.client.gui.Gui].
*/
@Suppress("FunctionName")
interface MCGuiWithSideChat {
interface DGuiWithSideChat {
fun `recode$getSideChat`(): SideChat
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import io.github.homchom.recode.feature.feature
import io.github.homchom.recode.id
import io.github.homchom.recode.render.IntegralColor
import io.github.homchom.recode.render.toColor
import io.github.homchom.recode.ui.text
import io.github.homchom.recode.ui.text.style
import io.github.homchom.recode.ui.text.text
import io.github.homchom.recode.ui.text.toVanilla
import net.fabricmc.fabric.api.resource.ResourceManagerHelper
import net.fabricmc.fabric.api.resource.ResourcePackActivationType

Expand All @@ -24,8 +26,8 @@ private fun registerBuiltInResourcePack(
Recode,
text {
literal("[$MOD_ID] ")
color(displayColor) { translate("resourcePack.recode.$id") }
},
translate("resourcePack.recode.$id", style().color(displayColor))
}.toVanilla(),
activationType
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package io.github.homchom.recode.feature.visual

import io.github.homchom.recode.hypercube.CommandAliasGroup
import io.github.homchom.recode.hypercube.DFValueMeta
import io.github.homchom.recode.hypercube.dfMiniMessage
import io.github.homchom.recode.hypercube.dfValueMeta
import io.github.homchom.recode.ui.text.*
import io.github.homchom.recode.util.regex.regex
import net.kyori.adventure.text.BuildableComponent
import net.kyori.adventure.text.TextComponent
import net.minecraft.util.FormattedCharSequence
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack

data class HighlightedExpression(val text: FormattedCharSequence, val preview: FormattedCharSequence?)

/**
* An object that highlights and caches DF value and MiniMessage expressions.
*
* @see runHighlighting
*/
class ExpressionHighlighter {
private val codes = setOf(
"default",
"selected",
"uuid",
"var",
"math",
"damager",
"killer",
"shooter",
"victim",
"projectile",
"random",
"round",
"index",
"entry"
)

private val highlightedCommands = buildList {
fun addGroup(
group: CommandAliasGroup,
highlightedArgumentIndex: Int = 0,
hasCount: Boolean = false,
parseMiniMessage: Boolean = true
) {
addAll(group.map { prefix ->
CommandInfo(prefix, highlightedArgumentIndex, hasCount, parseMiniMessage)
})
}

addGroup(CommandAliasGroup.NUMBER, hasCount = true, parseMiniMessage = false)
addGroup(CommandAliasGroup.STRING, hasCount = true, parseMiniMessage = false)
addGroup(CommandAliasGroup.TEXT, hasCount = true)
addGroup(CommandAliasGroup.VARIABLE, hasCount = true, parseMiniMessage = false)
addGroup(CommandAliasGroup.ITEM_NAME)
addGroup(CommandAliasGroup.ITEM_LORE_ADD)
addGroup(CommandAliasGroup.ITEM_LORE_SET, highlightedArgumentIndex = 1)
addGroup(CommandAliasGroup.PLOT_NAME)
addGroup(CommandAliasGroup.RELORE)
}

private data class CommandInfo(
val prefix: String,
val highlightedArgumentIndex: Int,
val hasCount: Boolean,
val parseMiniMessage: Boolean
)

// TODO: new color scheme?
private val colors = listOf(
0xffd600,
0x33ff00,
0x00ffe0,
0x5e77f7,
0xca64fa,
0xff4242
)

private val codeRegex = regex {
group {
str("%")
any("a-zA-Z").oneOrMore()
str("(").optional()
}
or; str(")")
or; end
}

private var cachedInput = ""
private var cachedHighlight = HighlightedExpression(FormattedCharSequence.EMPTY, null)

private val countRegex = regex {
space
digit.oneOrMore()
end
}

private fun leadingArgumentsRegex(highlightIndex: Int) = regex {
group {
none(" ").oneOrMore()
space
} * (highlightIndex - 1)
none(" ").oneOrMore()
space.optional()
}

fun runHighlighting(
chatInput: String,
formatted: FormattedCharSequence,
player: Player
): HighlightedExpression? {
if (cachedInput == chatInput) return cachedHighlight

val highlight = highlight(chatInput, formatted, player.mainHandItem)
if (highlight != null) {
cachedInput = chatInput
cachedHighlight = highlight
}
return highlight
}

private fun highlight(
chatInput: String,
formatted: FormattedCharSequence,
mainHandItem: ItemStack
): HighlightedExpression? {
// highlight commands
if (chatInput.startsWith('/')) {
val command = highlightedCommands.firstNotNullOfOrNull { info ->
info.takeIf { chatInput.startsWith(info.prefix, 1) }
} ?: return null
return highlightCommand(chatInput, formatted, command)
}

// highlight values
val valueMeta = mainHandItem.dfValueMeta()
if (valueMeta is DFValueMeta.Primitive || valueMeta is DFValueMeta.Variable) {
return highlightString(chatInput, valueMeta.type == "comp")
}

return null
}

private fun highlightString(string: String, parseMiniMessage: Boolean = true): HighlightedExpression {
val builder = TextBuilder()
var sliceStart = 0
var depth = 0

for (match in codeRegex.findAll(string)) {
builder.literal(string.substring(sliceStart, match.range.first), styleAt(depth))

val code = match.value
if (code == ")") {
if (depth > 0) depth--
} else depth++

val style = if (code.length > 1 && code.drop(1).removeSuffix("(") !in codes) {
style().red()
} else {
styleAt(depth)
}
builder.literal(string.substring(match.range), style)

if (code.endsWith('(')) depth++ else {
if (depth > 0) depth--
}

sliceStart = match.range.last + 1
}

if (parseMiniMessage) builder.raw.mapChildren { text ->
if (text is TextComponent && text.style().isEmpty) {
MiniMessageHighlighter.highlight(text.content()) as BuildableComponent<*, *>
} else {
text
}
}

val text = builder.build().toFormattedCharSequence(false)
val preview = if (parseMiniMessage) {
dfMiniMessage.deserialize(string).toFormattedCharSequence()
} else null
return HighlightedExpression(text, preview)
}

private fun highlightCommand(
input: String,
formatted: FormattedCharSequence,
info: CommandInfo
): HighlightedExpression? {
var startIndex = info.prefix.length + 2
var endIndex = input.length
if (startIndex > input.lastIndex) return null

if (info.highlightedArgumentIndex > 0) {
val regex = leadingArgumentsRegex(info.highlightedArgumentIndex)
val match = regex.find(input, startIndex)
if (match != null) {
startIndex = match.range.last + 1
if (startIndex > input.lastIndex) return null
}

}
if (info.hasCount) {
val match = countRegex.find(input, startIndex)
if (match != null) endIndex = match.range.first
}

val highlighted = highlightString(input.substring(startIndex, endIndex), info.parseMiniMessage)
val combined = formatted.replaceRange(startIndex..<endIndex, highlighted.text)
return HighlightedExpression(combined, highlighted.preview)
}

private fun styleAt(depth: Int) = if (depth == 0) {
style()
} else {
style().color(colors[depth - 1 % colors.size])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ package io.github.homchom.recode.game
import kotlin.time.Duration.Companion.milliseconds

/**
* Returns this integer as a [kotlin.time.Duration] in ticks, where 20 ticks = 1 second.
* @return this integer as a [kotlin.time.Duration] in ticks, where 20 ticks = 1 second.
*/
val Int.ticks get() = milliseconds * 50
29 changes: 29 additions & 0 deletions src/main/java/io/github/homchom/recode/game/ItemExtensions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@file:JvmName("ItemExtensions")

package io.github.homchom.recode.game

import io.github.homchom.recode.mixin.game.ItemStackAccessor
import io.github.homchom.recode.ui.text.mergeStyle
import io.github.homchom.recode.ui.text.toAdventure
import net.kyori.adventure.text.Component
import net.kyori.adventure.text.serializer.json.JSONComponentSerializer
import net.minecraft.nbt.Tag
import net.minecraft.world.item.ItemStack

/**
* @return this [ItemStack]'s "lore" (description), found in its tooltip, as a list of [Component]s.
*/
fun ItemStack.lore(): List<Component> {
val loreTag = tag
?.getCompoundOrNull("display")
?.getListOrNull("Lore", Tag.TAG_STRING)
?: return emptyList()

return buildList(loreTag.size) {
for (index in 0..<loreTag.size) {
val text = JSONComponentSerializer.json().deserializeOrNull(loreTag.getString(index)) ?: continue
val style = ItemStackAccessor.getLoreVanillaStyle().toAdventure()
add(text.mergeStyle(style))
}
}
}
Loading

0 comments on commit bb19cf7

Please sign in to comment.