cli: auto completions

This commit is contained in:
Bixilon 2022-05-29 19:26:22 +02:00
parent 805edb8590
commit 7191fdcb76
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
9 changed files with 92 additions and 15 deletions

View File

@ -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)

View File

@ -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)
}

View File

@ -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

View File

@ -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
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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<TrailingTextArgument> { (createCommand().execute(CommandReader("0_literal test"), CommandStack())) }
assertThrows<TrailingTextError> { (createCommand().execute(CommandReader("0_literal test"), CommandStack())) }
}
@Test
@ -115,6 +115,6 @@ internal class ExecutionChildReadingTest {
@Test
fun testTrailingTextEmptyRootNode() {
assertThrows<TrailingTextArgument> { RootNode().execute(CommandReader("trailing"), CommandStack()) }
assertThrows<TrailingTextError> { RootNode().execute(CommandReader("trailing"), CommandStack()) }
}
}

View File

@ -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<TrailingTextArgument> { (createCommand().getSuggestions(CommandReader("2_literal test"), CommandStack())) }
assertThrows<TrailingTextError> { (createCommand().getSuggestions(CommandReader("2_literal test"), CommandStack())) }
}
@Test
@ -110,7 +110,7 @@ internal class SuggestionChildReadingTest {
@Test
fun test2TrailingData() {
assertThrows<TrailingTextArgument> { (createCommand().getSuggestions(CommandReader("2_literal 1_literal_2 test"), CommandStack())) }
assertThrows<TrailingTextError> { (createCommand().getSuggestions(CommandReader("2_literal 1_literal_2 test"), CommandStack())) }
}
@Test
@ -135,7 +135,7 @@ internal class SuggestionChildReadingTest {
@Test
fun testTrailingTextEmptyNode() {
assertThrows<TrailingTextArgument> { RootNode().getSuggestions(CommandReader("trailing"), CommandStack()) }
assertThrows<TrailingTextError> { RootNode().getSuggestions(CommandReader("trailing"), CommandStack()) }
}
@Test

View File

@ -48,4 +48,39 @@ internal class KUtilTest {
fun testInvalidSizeArrayModify() {
assertThrows<IllegalArgumentException> { 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)
}
}