mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-24 04:57:50 -04:00
cli: auto completions
This commit is contained in:
parent
805edb8590
commit
7191fdcb76
@ -16,6 +16,6 @@ package de.bixilon.minosoft.commands.errors.literal
|
|||||||
import de.bixilon.minosoft.commands.errors.ReaderError
|
import de.bixilon.minosoft.commands.errors.ReaderError
|
||||||
import de.bixilon.minosoft.commands.util.CommandReader
|
import de.bixilon.minosoft.commands.util.CommandReader
|
||||||
|
|
||||||
class TrailingTextArgument(
|
class TrailingTextError(
|
||||||
reader: CommandReader,
|
reader: CommandReader,
|
||||||
) : ReaderError(reader, reader.pointer, reader.length)
|
) : ReaderError(reader, reader.pointer, reader.length)
|
@ -14,7 +14,7 @@
|
|||||||
package de.bixilon.minosoft.commands.nodes
|
package de.bixilon.minosoft.commands.nodes
|
||||||
|
|
||||||
import de.bixilon.minosoft.commands.errors.DeadEndError
|
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.stack.CommandStack
|
||||||
import de.bixilon.minosoft.commands.util.CommandReader
|
import de.bixilon.minosoft.commands.util.CommandReader
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ abstract class CommandNode(
|
|||||||
try {
|
try {
|
||||||
executeChild(child, reader, stack)
|
executeChild(child, reader, stack)
|
||||||
if (reader.canPeek()) {
|
if (reader.canPeek()) {
|
||||||
throw TrailingTextArgument(reader)
|
throw TrailingTextError(reader)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} catch (error: Throwable) {
|
} catch (error: Throwable) {
|
||||||
@ -78,7 +78,7 @@ abstract class CommandNode(
|
|||||||
try {
|
try {
|
||||||
val childSuggestions = child.getSuggestions(reader, stack)
|
val childSuggestions = child.getSuggestions(reader, stack)
|
||||||
if (reader.canPeek()) {
|
if (reader.canPeek()) {
|
||||||
throw TrailingTextArgument(reader)
|
throw TrailingTextError(reader)
|
||||||
}
|
}
|
||||||
parserSucceeds++
|
parserSucceeds++
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ abstract class CommandNode(
|
|||||||
protected fun checkForDeadEnd(reader: CommandReader) {
|
protected fun checkForDeadEnd(reader: CommandReader) {
|
||||||
if (children.isEmpty()) {
|
if (children.isEmpty()) {
|
||||||
if (reader.canPeek()) {
|
if (reader.canPeek()) {
|
||||||
throw TrailingTextArgument(reader)
|
throw TrailingTextError(reader)
|
||||||
} else {
|
} else {
|
||||||
throw DeadEndError(reader)
|
throw DeadEndError(reader)
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ open class TextInputElement(
|
|||||||
textElement.unmark()
|
textElement.unmark()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun _set(value: String) {
|
protected fun _set(value: String) {
|
||||||
val previous = _value.toString()
|
val previous = _value.toString()
|
||||||
val next = _value.replace(0, _value.length, value)
|
val next = _value.replace(0, _value.length, value)
|
||||||
_pointer = value.length
|
_pointer = value.length
|
||||||
|
@ -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.gui.rendering.system.window.KeyChangeTypes
|
||||||
import de.bixilon.minosoft.util.KUtil
|
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 suggestionText = Array(MAX_SUGGESTIONS) { TextElement(guiRenderer, ChatComponent.EMPTY).apply { prefMaxSize = Vec2i(300, Font.TOTAL_CHAR_HEIGHT) } }
|
||||||
private var textCount = 0
|
private var textCount = 0
|
||||||
private var offset = 0
|
private var offset = 0
|
||||||
@ -56,6 +56,15 @@ class NodeSuggestionsElement(guiRenderer: GUIRenderer, position: Vec2i) : Popper
|
|||||||
field = value
|
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?) {
|
override fun forceRender(offset: Vec2i, consumer: GUIVertexConsumer, options: GUIVertexOptions?) {
|
||||||
super.forceRender(offset, consumer, options)
|
super.forceRender(offset, consumer, options)
|
||||||
@ -114,6 +123,9 @@ class NodeSuggestionsElement(guiRenderer: GUIRenderer, position: Vec2i) : Popper
|
|||||||
if (type == KeyChangeTypes.RELEASE) {
|
if (type == KeyChangeTypes.RELEASE) {
|
||||||
return super.onKey(key, type)
|
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) {
|
val offset = when (key) {
|
||||||
KeyCodes.KEY_UP -> -1
|
KeyCodes.KEY_UP -> -1
|
||||||
KeyCodes.KEY_PAGE_UP -> -5
|
KeyCodes.KEY_PAGE_UP -> -5
|
||||||
@ -134,6 +146,11 @@ class NodeSuggestionsElement(guiRenderer: GUIRenderer, position: Vec2i) : Popper
|
|||||||
offset = 0
|
offset = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun applySuggestion(): Boolean {
|
||||||
|
inputElement.updateSuggestion(activeSuggestion ?: return false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val MAX_SUGGESTIONS = 10
|
const val MAX_SUGGESTIONS = 10
|
||||||
}
|
}
|
||||||
|
@ -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.gui.mesh.GUIVertexOptions
|
||||||
import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes
|
import de.bixilon.minosoft.gui.rendering.system.window.KeyChangeTypes
|
||||||
import de.bixilon.minosoft.gui.rendering.util.vec.vec2.Vec2iUtil.EMPTY
|
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.Log
|
||||||
import de.bixilon.minosoft.util.logging.LogLevels
|
import de.bixilon.minosoft.util.logging.LogLevels
|
||||||
import de.bixilon.minosoft.util.logging.LogMessageType
|
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) {
|
) : TextInputElement(guiRenderer, value, maxLength, cursorStyles, editable, onChange, background, shadow, scale, cutAtSize, parent) {
|
||||||
private var showError = false
|
private var showError = false
|
||||||
private val errorElement = NodeErrorElement(guiRenderer, Vec2i.EMPTY)
|
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?) {
|
override fun forceRender(offset: Vec2i, consumer: GUIVertexConsumer, options: GUIVertexOptions?) {
|
||||||
@ -113,4 +114,10 @@ class NodeTextInputElement(
|
|||||||
suggestions.onClose()
|
suggestions.onClose()
|
||||||
errorElement.onClose()
|
errorElement.onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateSuggestion(suggestion: Any) {
|
||||||
|
val string = suggestion.toString()
|
||||||
|
_set(value + string.substring(KUtil.getOverlappingText(value, string), string.length))
|
||||||
|
forceApply()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,4 +294,22 @@ object KUtil {
|
|||||||
|
|
||||||
return ret
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ package de.bixilon.minosoft.commands.nodes
|
|||||||
import de.bixilon.minosoft.commands.errors.DeadEndError
|
import de.bixilon.minosoft.commands.errors.DeadEndError
|
||||||
import de.bixilon.minosoft.commands.errors.literal.ExpectedLiteralArgument
|
import de.bixilon.minosoft.commands.errors.literal.ExpectedLiteralArgument
|
||||||
import de.bixilon.minosoft.commands.errors.literal.InvalidLiteralArgumentError
|
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.errors.reader.ExpectedWhitespaceError
|
||||||
import de.bixilon.minosoft.commands.parser.brigadier.string.StringParseError
|
import de.bixilon.minosoft.commands.parser.brigadier.string.StringParseError
|
||||||
import de.bixilon.minosoft.commands.parser.brigadier.string.StringParser
|
import de.bixilon.minosoft.commands.parser.brigadier.string.StringParser
|
||||||
@ -90,7 +90,7 @@ internal class ExecutionChildReadingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTrailingData() {
|
fun testTrailingData() {
|
||||||
assertThrows<TrailingTextArgument> { (createCommand().execute(CommandReader("0_literal test"), CommandStack())) }
|
assertThrows<TrailingTextError> { (createCommand().execute(CommandReader("0_literal test"), CommandStack())) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -115,6 +115,6 @@ internal class ExecutionChildReadingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTrailingTextEmptyRootNode() {
|
fun testTrailingTextEmptyRootNode() {
|
||||||
assertThrows<TrailingTextArgument> { RootNode().execute(CommandReader("trailing"), CommandStack()) }
|
assertThrows<TrailingTextError> { RootNode().execute(CommandReader("trailing"), CommandStack()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ package de.bixilon.minosoft.commands.nodes
|
|||||||
|
|
||||||
import de.bixilon.minosoft.commands.errors.DeadEndError
|
import de.bixilon.minosoft.commands.errors.DeadEndError
|
||||||
import de.bixilon.minosoft.commands.errors.literal.InvalidLiteralArgumentError
|
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.errors.reader.ExpectedWhitespaceError
|
||||||
import de.bixilon.minosoft.commands.parser.brigadier.string.StringParseError
|
import de.bixilon.minosoft.commands.parser.brigadier.string.StringParseError
|
||||||
import de.bixilon.minosoft.commands.parser.brigadier.string.StringParser
|
import de.bixilon.minosoft.commands.parser.brigadier.string.StringParser
|
||||||
@ -95,7 +95,7 @@ internal class SuggestionChildReadingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTrailingData() {
|
fun testTrailingData() {
|
||||||
assertThrows<TrailingTextArgument> { (createCommand().getSuggestions(CommandReader("2_literal test"), CommandStack())) }
|
assertThrows<TrailingTextError> { (createCommand().getSuggestions(CommandReader("2_literal test"), CommandStack())) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -110,7 +110,7 @@ internal class SuggestionChildReadingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test2TrailingData() {
|
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
|
@Test
|
||||||
@ -135,7 +135,7 @@ internal class SuggestionChildReadingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTrailingTextEmptyNode() {
|
fun testTrailingTextEmptyNode() {
|
||||||
assertThrows<TrailingTextArgument> { RootNode().getSuggestions(CommandReader("trailing"), CommandStack()) }
|
assertThrows<TrailingTextError> { RootNode().getSuggestions(CommandReader("trailing"), CommandStack()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -48,4 +48,39 @@ internal class KUtilTest {
|
|||||||
fun testInvalidSizeArrayModify() {
|
fun testInvalidSizeArrayModify() {
|
||||||
assertThrows<IllegalArgumentException> { KUtil.modifyArrayIndex(0, 0) }
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user