diff --git a/src/main/java/de/bixilon/minosoft/commands/errors/literal/TrailingTextArgument.kt b/src/main/java/de/bixilon/minosoft/commands/errors/literal/TrailingTextError.kt similarity index 97% rename from src/main/java/de/bixilon/minosoft/commands/errors/literal/TrailingTextArgument.kt rename to src/main/java/de/bixilon/minosoft/commands/errors/literal/TrailingTextError.kt index ba427be4b..1db8917e0 100644 --- a/src/main/java/de/bixilon/minosoft/commands/errors/literal/TrailingTextArgument.kt +++ b/src/main/java/de/bixilon/minosoft/commands/errors/literal/TrailingTextError.kt @@ -16,6 +16,6 @@ package de.bixilon.minosoft.commands.errors.literal import de.bixilon.minosoft.commands.errors.ReaderError import de.bixilon.minosoft.commands.util.CommandReader -class TrailingTextArgument( +class TrailingTextError( reader: CommandReader, ) : ReaderError(reader, reader.pointer, reader.length) diff --git a/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt b/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt index 3284b44f8..94dff5112 100644 --- a/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt +++ b/src/main/java/de/bixilon/minosoft/commands/nodes/CommandNode.kt @@ -14,7 +14,7 @@ package de.bixilon.minosoft.commands.nodes import de.bixilon.minosoft.commands.errors.DeadEndError -import de.bixilon.minosoft.commands.errors.literal.TrailingTextArgument +import de.bixilon.minosoft.commands.errors.literal.TrailingTextError import de.bixilon.minosoft.commands.stack.CommandStack import de.bixilon.minosoft.commands.util.CommandReader @@ -47,7 +47,7 @@ abstract class CommandNode( try { executeChild(child, reader, stack) if (reader.canPeek()) { - throw TrailingTextArgument(reader) + throw TrailingTextError(reader) } return } catch (error: Throwable) { @@ -78,7 +78,7 @@ abstract class CommandNode( try { val childSuggestions = child.getSuggestions(reader, stack) if (reader.canPeek()) { - throw TrailingTextArgument(reader) + throw TrailingTextError(reader) } parserSucceeds++ @@ -115,7 +115,7 @@ abstract class CommandNode( protected fun checkForDeadEnd(reader: CommandReader) { if (children.isEmpty()) { if (reader.canPeek()) { - throw TrailingTextArgument(reader) + throw TrailingTextError(reader) } else { throw DeadEndError(reader) } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt index 1e1a19629..f21a4778d 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/TextInputElement.kt @@ -98,7 +98,7 @@ open class TextInputElement( textElement.unmark() } - private fun _set(value: String) { + protected fun _set(value: String) { val previous = _value.toString() val next = _value.replace(0, _value.length, value) _pointer = value.length diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/node/NodeSuggestionsElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/node/NodeSuggestionsElement.kt index 593589fe5..b538cd7e2 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/node/NodeSuggestionsElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/node/NodeSuggestionsElement.kt @@ -27,7 +27,7 @@ import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes import de.bixilon.minosoft.util.KUtil -class NodeSuggestionsElement(guiRenderer: GUIRenderer, position: Vec2i) : Popper(guiRenderer, position) { +class NodeSuggestionsElement(guiRenderer: GUIRenderer, position: Vec2i, val inputElement: NodeTextInputElement) : Popper(guiRenderer, position) { private var suggestionText = Array(MAX_SUGGESTIONS) { TextElement(guiRenderer, ChatComponent.EMPTY).apply { prefMaxSize = Vec2i(300, Font.TOTAL_CHAR_HEIGHT) } } private var textCount = 0 private var offset = 0 @@ -56,6 +56,15 @@ class NodeSuggestionsElement(guiRenderer: GUIRenderer, position: Vec2i) : Popper field = value } + val activeSuggestion: Any? + get() { + val suggestions = suggestions ?: return null + if (suggestions.isEmpty()) { + return null + } + return suggestions[offset] + } + override fun forceRender(offset: Vec2i, consumer: GUIVertexConsumer, options: GUIVertexOptions?) { super.forceRender(offset, consumer, options) @@ -114,6 +123,9 @@ class NodeSuggestionsElement(guiRenderer: GUIRenderer, position: Vec2i) : Popper if (type == KeyChangeTypes.RELEASE) { return super.onKey(key, type) } + if (key == KeyCodes.KEY_ENTER || key == KeyCodes.KEY_KP_ENTER || key == KeyCodes.KEY_TAB) { + return applySuggestion() + } val offset = when (key) { KeyCodes.KEY_UP -> -1 KeyCodes.KEY_PAGE_UP -> -5 @@ -134,6 +146,11 @@ class NodeSuggestionsElement(guiRenderer: GUIRenderer, position: Vec2i) : Popper offset = 0 } + fun applySuggestion(): Boolean { + inputElement.updateSuggestion(activeSuggestion ?: return false) + return true + } + private companion object { const val MAX_SUGGESTIONS = 10 } diff --git a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/node/NodeTextInputElement.kt b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/node/NodeTextInputElement.kt index 9aea07f3b..ae65a4dcd 100644 --- a/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/node/NodeTextInputElement.kt +++ b/src/main/java/de/bixilon/minosoft/gui/rendering/gui/gui/elements/input/node/NodeTextInputElement.kt @@ -28,6 +28,7 @@ import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexConsumer import de.bixilon.minosoft.gui.rendering.gui.mesh.GUIVertexOptions import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY +import de.bixilon.minosoft.util.KUtil import de.bixilon.minosoft.util.logging.Log import de.bixilon.minosoft.util.logging.LogLevels import de.bixilon.minosoft.util.logging.LogMessageType @@ -48,7 +49,7 @@ class NodeTextInputElement( ) : TextInputElement(guiRenderer, value, maxLength, cursorStyles, editable, onChange, background, shadow, scale, cutAtSize, parent) { private var showError = false private val errorElement = NodeErrorElement(guiRenderer, Vec2i.EMPTY) - private val suggestions = NodeSuggestionsElement(guiRenderer, Vec2i.EMPTY) + private val suggestions = NodeSuggestionsElement(guiRenderer, Vec2i.EMPTY, this) override fun forceRender(offset: Vec2i, consumer: GUIVertexConsumer, options: GUIVertexOptions?) { @@ -113,4 +114,10 @@ class NodeTextInputElement( suggestions.onClose() errorElement.onClose() } + + fun updateSuggestion(suggestion: Any) { + val string = suggestion.toString() + _set(value + string.substring(KUtil.getOverlappingText(value, string), string.length)) + forceApply() + } } diff --git a/src/main/java/de/bixilon/minosoft/util/KUtil.kt b/src/main/java/de/bixilon/minosoft/util/KUtil.kt index e2ab20f24..118545dff 100644 --- a/src/main/java/de/bixilon/minosoft/util/KUtil.kt +++ b/src/main/java/de/bixilon/minosoft/util/KUtil.kt @@ -294,4 +294,22 @@ object KUtil { return ret } + + fun getOverlappingText(start: String, end: String): Int { + var overlapping = 0 + + shift@ for (shift in 1..end.length) { + if (start.length < shift) { + break + } + for (i in 0 until shift) { + if (end.codePointAt(i) != start.codePointAt(start.length - (shift - i))) { + continue@shift + } + } + overlapping = shift + } + + return overlapping + } } diff --git a/src/test/java/de/bixilon/minosoft/commands/nodes/ExecutionChildReadingTest.kt b/src/test/java/de/bixilon/minosoft/commands/nodes/ExecutionChildReadingTest.kt index 479d3ae9d..2054e3ee3 100644 --- a/src/test/java/de/bixilon/minosoft/commands/nodes/ExecutionChildReadingTest.kt +++ b/src/test/java/de/bixilon/minosoft/commands/nodes/ExecutionChildReadingTest.kt @@ -16,7 +16,7 @@ package de.bixilon.minosoft.commands.nodes import de.bixilon.minosoft.commands.errors.DeadEndError import de.bixilon.minosoft.commands.errors.literal.ExpectedLiteralArgument import de.bixilon.minosoft.commands.errors.literal.InvalidLiteralArgumentError -import de.bixilon.minosoft.commands.errors.literal.TrailingTextArgument +import de.bixilon.minosoft.commands.errors.literal.TrailingTextError import de.bixilon.minosoft.commands.errors.reader.ExpectedWhitespaceError import de.bixilon.minosoft.commands.parser.brigadier.string.StringParseError import de.bixilon.minosoft.commands.parser.brigadier.string.StringParser @@ -90,7 +90,7 @@ internal class ExecutionChildReadingTest { @Test fun testTrailingData() { - assertThrows { (createCommand().execute(CommandReader("0_literal test"), CommandStack())) } + assertThrows { (createCommand().execute(CommandReader("0_literal test"), CommandStack())) } } @Test @@ -115,6 +115,6 @@ internal class ExecutionChildReadingTest { @Test fun testTrailingTextEmptyRootNode() { - assertThrows { RootNode().execute(CommandReader("trailing"), CommandStack()) } + assertThrows { RootNode().execute(CommandReader("trailing"), CommandStack()) } } } diff --git a/src/test/java/de/bixilon/minosoft/commands/nodes/SuggestionChildReadingTest.kt b/src/test/java/de/bixilon/minosoft/commands/nodes/SuggestionChildReadingTest.kt index 0b117c82f..e2b22677f 100644 --- a/src/test/java/de/bixilon/minosoft/commands/nodes/SuggestionChildReadingTest.kt +++ b/src/test/java/de/bixilon/minosoft/commands/nodes/SuggestionChildReadingTest.kt @@ -15,7 +15,7 @@ package de.bixilon.minosoft.commands.nodes import de.bixilon.minosoft.commands.errors.DeadEndError import de.bixilon.minosoft.commands.errors.literal.InvalidLiteralArgumentError -import de.bixilon.minosoft.commands.errors.literal.TrailingTextArgument +import de.bixilon.minosoft.commands.errors.literal.TrailingTextError import de.bixilon.minosoft.commands.errors.reader.ExpectedWhitespaceError import de.bixilon.minosoft.commands.parser.brigadier.string.StringParseError import de.bixilon.minosoft.commands.parser.brigadier.string.StringParser @@ -95,7 +95,7 @@ internal class SuggestionChildReadingTest { @Test fun testTrailingData() { - assertThrows { (createCommand().getSuggestions(CommandReader("2_literal test"), CommandStack())) } + assertThrows { (createCommand().getSuggestions(CommandReader("2_literal test"), CommandStack())) } } @Test @@ -110,7 +110,7 @@ internal class SuggestionChildReadingTest { @Test fun test2TrailingData() { - assertThrows { (createCommand().getSuggestions(CommandReader("2_literal 1_literal_2 test"), CommandStack())) } + assertThrows { (createCommand().getSuggestions(CommandReader("2_literal 1_literal_2 test"), CommandStack())) } } @Test @@ -135,7 +135,7 @@ internal class SuggestionChildReadingTest { @Test fun testTrailingTextEmptyNode() { - assertThrows { RootNode().getSuggestions(CommandReader("trailing"), CommandStack()) } + assertThrows { RootNode().getSuggestions(CommandReader("trailing"), CommandStack()) } } @Test diff --git a/src/test/java/de/bixilon/minosoft/util/KUtilTest.kt b/src/test/java/de/bixilon/minosoft/util/KUtilTest.kt index 2c13b0fa2..7edb952f1 100644 --- a/src/test/java/de/bixilon/minosoft/util/KUtilTest.kt +++ b/src/test/java/de/bixilon/minosoft/util/KUtilTest.kt @@ -48,4 +48,39 @@ internal class KUtilTest { fun testInvalidSizeArrayModify() { assertThrows { KUtil.modifyArrayIndex(0, 0) } } + + @Test + fun testNonOverlappingString() { + assertEquals(KUtil.getOverlappingText("test", "invalid"), 0) + } + + @Test + fun testEmptyOverlappingText() { + assertEquals(KUtil.getOverlappingText("", ""), 0) + } + + @Test + fun testFullyOverlapping() { + assertEquals(KUtil.getOverlappingText("next", "next"), 4) + } + + @Test + fun testSingleChatOverlapping() { + assertEquals(KUtil.getOverlappingText("next", "test"), 1) + } + + @Test + fun testSingleChatOverlapping2() { + assertEquals(KUtil.getOverlappingText("n", "nix"), 1) + } + + @Test + fun testSingleChatOverlapping3() { + assertEquals(KUtil.getOverlappingText("nix", "x"), 1) + } + + @Test + fun testSingleChatOverlapping4() { + assertEquals(KUtil.getOverlappingText("nix", "ix"), 2) + } }