Text input selection test
This commit is contained in:
parent
409c5bb443
commit
b62ac72bc5
@ -1,10 +1,16 @@
|
||||
package ru.dbotthepony.mc.otm.client.screen.panels
|
||||
|
||||
import com.mojang.blaze3d.platform.GlStateManager
|
||||
import com.mojang.blaze3d.platform.InputConstants
|
||||
import com.mojang.blaze3d.systems.RenderSystem
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat
|
||||
import com.mojang.blaze3d.vertex.PoseStack
|
||||
import com.mojang.blaze3d.vertex.VertexFormat
|
||||
import it.unimi.dsi.fastutil.chars.CharOpenHashSet
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap
|
||||
import net.minecraft.client.gui.screens.Screen
|
||||
import net.minecraft.client.renderer.GameRenderer
|
||||
import ru.dbotthepony.mc.otm.client.isCtrlDown
|
||||
import ru.dbotthepony.mc.otm.client.isKeyDown
|
||||
import ru.dbotthepony.mc.otm.client.isShiftDown
|
||||
@ -12,6 +18,8 @@ import ru.dbotthepony.mc.otm.client.minecraft
|
||||
import ru.dbotthepony.mc.otm.client.render.DynamicBufferSource
|
||||
import ru.dbotthepony.mc.otm.client.render.TextAlign
|
||||
import ru.dbotthepony.mc.otm.client.render.drawAligned
|
||||
import ru.dbotthepony.mc.otm.client.render.tesselator
|
||||
import ru.dbotthepony.mc.otm.core.addAll
|
||||
import ru.dbotthepony.mc.otm.core.math.RGBAColor
|
||||
import ru.dbotthepony.mc.otm.milliTime
|
||||
|
||||
@ -23,10 +31,39 @@ open class TextInputPanel<out S : Screen>(
|
||||
width: Float = 10f,
|
||||
height: Float = 10f,
|
||||
) : EditablePanel<S>(screen, parent, x, y, width, height) {
|
||||
data class TextSelection(val start: Int, val end: Int) {
|
||||
init {
|
||||
require(start <= end) { "$start <= $end" }
|
||||
require(start >= 0) { "$start >= 0" }
|
||||
private data class TextSelection(val start: Int, val end: Int) {
|
||||
val isInversed get() = start > end
|
||||
val isNotEmpty get() = start != end
|
||||
|
||||
val actualStart get() = if (isInversed) this.end else this.start
|
||||
val actualEnd get() = if (isInversed) this.start else this.end
|
||||
|
||||
fun sub(line: String): Pair<String, String> {
|
||||
val before = line.substring(0, actualStart.coerceIn(0, line.length))
|
||||
val selected = line.substring(actualStart.coerceIn(0, line.length), actualEnd.coerceAtMost(line.length))
|
||||
|
||||
return before to selected
|
||||
}
|
||||
|
||||
fun coversEntireString(value: String?): Boolean {
|
||||
if (value == null)
|
||||
return false
|
||||
|
||||
return value.length <= actualEnd && actualStart <= 0
|
||||
}
|
||||
|
||||
fun coversEntireLine(value: String?): Boolean {
|
||||
if (value == null)
|
||||
return true
|
||||
|
||||
return value.length < actualEnd && actualStart <= 0
|
||||
}
|
||||
|
||||
fun coversNewline(value: String?): Boolean {
|
||||
if (value == null)
|
||||
return actualEnd == Int.MAX_VALUE
|
||||
|
||||
return value.length < actualEnd
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,10 +71,13 @@ open class TextInputPanel<out S : Screen>(
|
||||
private val lines = ArrayList(this@TextInputPanel.lines) // ultra fast copy
|
||||
private val cursorLine = this@TextInputPanel.cursorLine
|
||||
private val cursorCharacter = this@TextInputPanel.cursorCharacter
|
||||
private val selections = Int2ObjectAVLTreeMap(this@TextInputPanel.selections)
|
||||
|
||||
fun apply() {
|
||||
this@TextInputPanel.lines.clear()
|
||||
this@TextInputPanel.lines.addAll(lines)
|
||||
this@TextInputPanel.selections.clear()
|
||||
this@TextInputPanel.selections.putAll(selections)
|
||||
this@TextInputPanel.cursorCharacter = cursorCharacter
|
||||
this@TextInputPanel.cursorLine = cursorLine
|
||||
}
|
||||
@ -51,6 +91,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
if (lines != other.lines) return false
|
||||
if (cursorLine != other.cursorLine) return false
|
||||
if (cursorCharacter != other.cursorCharacter) return false
|
||||
if (selections != other.selections) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@ -73,7 +114,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
|
||||
private var textCache: String? = null
|
||||
private val lines = ArrayList<String>()
|
||||
private val selections = Int2ObjectOpenHashMap<TextSelection>()
|
||||
private val selections = Int2ObjectAVLTreeMap<TextSelection>()
|
||||
private val undo = ArrayDeque<Snapshot>()
|
||||
private val redo = ArrayDeque<Snapshot>()
|
||||
|
||||
@ -96,6 +137,11 @@ open class TextInputPanel<out S : Screen>(
|
||||
undo.addLast(snapshot)
|
||||
}
|
||||
|
||||
private fun pushbackSnapshotIfNoTimer() {
|
||||
if (snapshotTimer == null)
|
||||
pushbackSnapshot()
|
||||
}
|
||||
|
||||
private fun recordHistory(hard: Boolean = false) {
|
||||
if (hard) {
|
||||
pushbackSnapshot()
|
||||
@ -173,6 +219,19 @@ open class TextInputPanel<out S : Screen>(
|
||||
lines[index] = value
|
||||
}
|
||||
|
||||
private fun selectionIndex(index: Int) = object : Int2ObjectMap.Entry<TextSelection> {
|
||||
override fun setValue(newValue: TextSelection): TextSelection? {
|
||||
return selections.put(index, newValue)
|
||||
}
|
||||
|
||||
override fun getIntKey(): Int {
|
||||
return index
|
||||
}
|
||||
|
||||
override val value: TextSelection
|
||||
get() = selections[index] ?: throw NoSuchElementException()
|
||||
}
|
||||
|
||||
fun insertLine(index: Int, value: String = "") {
|
||||
lines.ensureCapacity(index)
|
||||
|
||||
@ -180,7 +239,8 @@ open class TextInputPanel<out S : Screen>(
|
||||
lines.add("")
|
||||
}
|
||||
|
||||
val upperLines = selections.int2ObjectEntrySet().filter { it.intKey >= index }
|
||||
val upperLines = ArrayList<Int2ObjectMap.Entry<TextSelection>>()
|
||||
upperLines.addAll(selections.int2ObjectEntrySet().iterator(selectionIndex(index)))
|
||||
|
||||
for (entry in upperLines) {
|
||||
selections.remove(entry.intKey)
|
||||
@ -207,7 +267,9 @@ open class TextInputPanel<out S : Screen>(
|
||||
|
||||
lines.removeAt(index)
|
||||
selections.remove(index)
|
||||
val upperLines = selections.int2ObjectEntrySet().filter { it.intKey > index }
|
||||
|
||||
val upperLines = ArrayList<Int2ObjectMap.Entry<TextSelection>>()
|
||||
upperLines.addAll(selections.int2ObjectEntrySet().iterator(selectionIndex(index)))
|
||||
|
||||
for (entry in upperLines) {
|
||||
selections.remove(entry.intKey)
|
||||
@ -220,7 +282,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
return true
|
||||
}
|
||||
|
||||
fun moveCursors(line: Int, character: Int, moveBy: Int) {
|
||||
protected fun moveCursors(line: Int, character: Int, moveBy: Int) {
|
||||
@Suppress("name_shadowing")
|
||||
val moveBy = if (character + moveBy < 0) -character else moveBy
|
||||
|
||||
@ -252,9 +314,24 @@ open class TextInputPanel<out S : Screen>(
|
||||
}
|
||||
}
|
||||
|
||||
fun wipeSelection() {
|
||||
protected fun wipeSelection() {
|
||||
if (selections.isEmpty())
|
||||
return
|
||||
|
||||
pushbackSnapshotIfNoTimer()
|
||||
|
||||
val inversed = ArrayList(selections.int2ObjectEntrySet())
|
||||
inversed.sortByDescending { it.intKey }
|
||||
|
||||
var downTo = inversed.size.ushr(1)
|
||||
if (inversed.size and 1 == 1) downTo++
|
||||
|
||||
for (i in inversed.size - 1 downTo downTo) {
|
||||
val i2 = inversed.size - i
|
||||
val a = inversed[i]
|
||||
val b = inversed[i2]
|
||||
inversed[i] = b
|
||||
inversed[i2] = a
|
||||
}
|
||||
|
||||
for ((lineNumber, selection) in inversed) {
|
||||
if (selection.start != selection.end) {
|
||||
@ -264,7 +341,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
}
|
||||
}
|
||||
|
||||
selections.clear()
|
||||
// selections.clear()
|
||||
}
|
||||
|
||||
protected open fun textChanged(oldText: String, newText: String) {}
|
||||
@ -294,18 +371,38 @@ open class TextInputPanel<out S : Screen>(
|
||||
lines.addAll(value.split(NEWLINES))
|
||||
}
|
||||
|
||||
fun advanceCursorLeft() {
|
||||
data class CursorAdvanceResult(
|
||||
val oldLine: Int,
|
||||
val newLine: Int,
|
||||
val oldCharacter: Int,
|
||||
val newCharacter: Int,
|
||||
val couldHaveChangedLine: Boolean
|
||||
) {
|
||||
val linesChanged get() = oldLine != newLine
|
||||
val charsChanged get() = linesChanged || oldCharacter != newCharacter
|
||||
val advancedChars get() = newCharacter - oldCharacter
|
||||
}
|
||||
|
||||
fun advanceCursorLeft(greedy: Boolean = false): CursorAdvanceResult {
|
||||
val oldLine = cursorLine
|
||||
val oldChar = cursorCharacter
|
||||
val line = this[cursorLine]
|
||||
var couldHaveChangedLine = false
|
||||
|
||||
if (line != null && cursorCharacter > line.length) {
|
||||
cursorCharacter = line.length - 1
|
||||
}
|
||||
|
||||
if (cursorCharacter > 0) {
|
||||
if (greedy && line != null) {
|
||||
cursorCharacter = greedyAdvanceLeft(line, cursorCharacter)
|
||||
} else {
|
||||
cursorCharacter--
|
||||
}
|
||||
} else if (cursorLine > 0) {
|
||||
cursorLine--
|
||||
cursorCharacter = 0
|
||||
couldHaveChangedLine = true
|
||||
|
||||
@Suppress("name_shadowing")
|
||||
val line = this[cursorLine]
|
||||
@ -313,15 +410,29 @@ open class TextInputPanel<out S : Screen>(
|
||||
if (line != null) {
|
||||
cursorCharacter = line.length
|
||||
}
|
||||
}
|
||||
} else {
|
||||
couldHaveChangedLine = true
|
||||
}
|
||||
|
||||
fun advanceCursorRight() {
|
||||
cursorCharacter++
|
||||
return CursorAdvanceResult(oldLine, cursorLine, oldChar, cursorCharacter, couldHaveChangedLine = couldHaveChangedLine)
|
||||
}
|
||||
|
||||
fun advanceCursorRight(greedy: Boolean = false): CursorAdvanceResult {
|
||||
val oldLine = cursorLine
|
||||
val oldChar = cursorCharacter
|
||||
var couldHaveChangedLine = false
|
||||
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (greedy && line != null && cursorCharacter + 1 < line.length) {
|
||||
cursorCharacter = greedyAdvanceRight(line, cursorCharacter)
|
||||
} else {
|
||||
cursorCharacter++
|
||||
}
|
||||
|
||||
if (line != null && cursorCharacter > line.length) {
|
||||
couldHaveChangedLine = true
|
||||
|
||||
if (lines.size <= cursorLine + 1) {
|
||||
cursorCharacter = line.length
|
||||
} else {
|
||||
@ -331,6 +442,8 @@ open class TextInputPanel<out S : Screen>(
|
||||
} else if (line == null) {
|
||||
cursorCharacter = 0
|
||||
}
|
||||
|
||||
return CursorAdvanceResult(oldLine, cursorLine, oldChar, cursorCharacter, couldHaveChangedLine = couldHaveChangedLine)
|
||||
}
|
||||
|
||||
override fun keyPressedInternal(key: Int, scancode: Int, mods: Int): Boolean {
|
||||
@ -394,7 +507,6 @@ open class TextInputPanel<out S : Screen>(
|
||||
if (multiLine) {
|
||||
val lines = if (selections.isNotEmpty()) selections.keys else listOf(cursorLine)
|
||||
|
||||
|
||||
if (minecraft.window.isKeyDown(InputConstants.KEY_RSHIFT) || minecraft.window.isKeyDown(InputConstants.KEY_LSHIFT)) {
|
||||
var hit = false
|
||||
|
||||
@ -429,36 +541,54 @@ open class TextInputPanel<out S : Screen>(
|
||||
}
|
||||
|
||||
if (key == InputConstants.KEY_LEFT) {
|
||||
if (minecraft.window.isCtrlDown) {
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line == null || cursorCharacter <= 0) {
|
||||
advanceCursorLeft()
|
||||
if (!minecraft.window.isShiftDown) {
|
||||
advanceCursorLeft(minecraft.window.isCtrlDown)
|
||||
selections.clear()
|
||||
} else {
|
||||
if (cursorCharacter >= line.length)
|
||||
cursorCharacter = line.length - 1
|
||||
if (this[cursorLine] == null)
|
||||
return true
|
||||
|
||||
cursorCharacter = greedyAdvanceLeft(line, cursorCharacter)
|
||||
val existing = selections[cursorLine]
|
||||
val result = advanceCursorLeft(minecraft.window.isCtrlDown)
|
||||
|
||||
if (!result.linesChanged)
|
||||
selections[result.oldLine] = TextSelection(
|
||||
existing?.start ?: result.oldCharacter,
|
||||
result.newCharacter)
|
||||
else {
|
||||
this[result.newLine]?.let {
|
||||
val existingNewline = selections[result.newLine]
|
||||
|
||||
if (existingNewline == null) {
|
||||
selections[result.newLine] = TextSelection(
|
||||
Int.MAX_VALUE,
|
||||
it.length)
|
||||
} else {
|
||||
if (!existingNewline.isInversed) {
|
||||
selections[result.newLine] = TextSelection(
|
||||
existingNewline.start,
|
||||
it.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
advanceCursorLeft()
|
||||
}
|
||||
|
||||
return true
|
||||
} else if (key == InputConstants.KEY_RIGHT) {
|
||||
if (minecraft.window.isCtrlDown) {
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line == null || cursorCharacter + 1 >= line.length) {
|
||||
advanceCursorRight()
|
||||
if (!minecraft.window.isShiftDown) {
|
||||
advanceCursorRight(minecraft.window.isCtrlDown)
|
||||
selections.clear()
|
||||
} else {
|
||||
if (cursorCharacter < 0)
|
||||
cursorCharacter = 0
|
||||
if (this[cursorLine] == null)
|
||||
return true
|
||||
|
||||
cursorCharacter = greedyAdvanceRight(line, cursorCharacter)
|
||||
}
|
||||
} else {
|
||||
advanceCursorRight()
|
||||
val existing = selections[cursorLine]
|
||||
val result = advanceCursorRight(minecraft.window.isCtrlDown)
|
||||
|
||||
selections[result.oldLine] = TextSelection(
|
||||
existing?.start ?: result.oldCharacter,
|
||||
if (result.couldHaveChangedLine) Int.MAX_VALUE else result.newCharacter)
|
||||
}
|
||||
|
||||
return true
|
||||
@ -467,23 +597,30 @@ open class TextInputPanel<out S : Screen>(
|
||||
cursorLine--
|
||||
}
|
||||
|
||||
selections.clear()
|
||||
|
||||
return true
|
||||
} else if (key == InputConstants.KEY_DOWN) {
|
||||
if (cursorLine < lines.size - 1) {
|
||||
cursorLine++
|
||||
}
|
||||
|
||||
selections.clear()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (key == InputConstants.KEY_BACKSPACE) {
|
||||
if (cursorLine <= 0 && cursorCharacter <= 0) {
|
||||
return true
|
||||
} else if (cursorCharacter <= 0) {
|
||||
wipeSelection()
|
||||
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (cursorLine <= 0 && cursorCharacter <= 0) {
|
||||
return true
|
||||
} else if (cursorCharacter <= 0 || line?.length == 0) {
|
||||
if (line == null) {
|
||||
cursorCharacter = this[--cursorLine]?.length ?: 0
|
||||
recordHistory()
|
||||
} else {
|
||||
removeLine(cursorLine)
|
||||
val newLine = this[cursorLine]
|
||||
@ -497,25 +634,33 @@ open class TextInputPanel<out S : Screen>(
|
||||
recordHistory()
|
||||
}
|
||||
} else {
|
||||
val line = this[cursorLine]
|
||||
|
||||
if (line != null) {
|
||||
var cursorCharacter = cursorCharacter
|
||||
|
||||
if (cursorCharacter >= line.length)
|
||||
cursorCharacter = line.length - 1
|
||||
pushbackSnapshotIfNoTimer()
|
||||
|
||||
// remove from very end
|
||||
if (cursorCharacter >= line.length) {
|
||||
val newLine = line.substring(0, line.length - 1)
|
||||
moveCursors(cursorLine, cursorCharacter, -1)
|
||||
this[cursorLine] = newLine
|
||||
recordHistory()
|
||||
} else {
|
||||
val newLine = line.substring(0, cursorCharacter - 1) + line.substring(cursorCharacter)
|
||||
moveCursors(cursorLine, cursorCharacter, -1)
|
||||
this[cursorLine] = newLine
|
||||
recordHistory()
|
||||
}
|
||||
} else if (cursorLine > 0) {
|
||||
cursorLine--
|
||||
recordHistory()
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if (key == InputConstants.KEY_DELETE) {
|
||||
wipeSelection()
|
||||
|
||||
if (cursorLine !in 0 until lines.size) {
|
||||
cursorLine = lines.size - 1
|
||||
return true
|
||||
@ -540,8 +685,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
return true
|
||||
}
|
||||
|
||||
if (snapshotTimer == null)
|
||||
pushbackSnapshot()
|
||||
pushbackSnapshotIfNoTimer()
|
||||
|
||||
val bottomLine = this[cursorLine + 1]!!
|
||||
cursorCharacter = line.length
|
||||
@ -550,8 +694,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
|
||||
recordHistory()
|
||||
} else {
|
||||
if (snapshotTimer == null)
|
||||
pushbackSnapshot()
|
||||
pushbackSnapshotIfNoTimer()
|
||||
|
||||
val cursorCharacter = cursorCharacter
|
||||
moveCursors(cursorLine, cursorCharacter, -1)
|
||||
@ -572,10 +715,19 @@ open class TextInputPanel<out S : Screen>(
|
||||
redo()
|
||||
}
|
||||
|
||||
if (key == InputConstants.KEY_A && minecraft.window.isCtrlDown) {
|
||||
selections.clear()
|
||||
|
||||
for ((i, line) in lines.withIndex()) {
|
||||
selections[i] = TextSelection(0, Int.MAX_VALUE)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun charTypedInternal(codepoint: Char, mods: Int): Boolean {
|
||||
wipeSelection()
|
||||
var line = this[cursorLine]
|
||||
|
||||
if (line == null) {
|
||||
@ -589,8 +741,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
else
|
||||
line = line.substring(0, cursorCharacter) + codepoint + line.substring(cursorCharacter)
|
||||
|
||||
if (snapshotTimer == null)
|
||||
pushbackSnapshot()
|
||||
pushbackSnapshotIfNoTimer()
|
||||
|
||||
set(cursorLine, line)
|
||||
moveCursors(cursorLine, cursorCharacter, 1)
|
||||
@ -602,7 +753,9 @@ open class TextInputPanel<out S : Screen>(
|
||||
override fun innerRender(stack: PoseStack, mouseX: Float, mouseY: Float, partialTick: Float) {
|
||||
var y = 0f
|
||||
|
||||
for (line in lines) {
|
||||
for ((i, line) in lines.withIndex()) {
|
||||
val selection = selections[i]
|
||||
|
||||
font.drawAligned(
|
||||
poseStack = stack,
|
||||
buffer = BUFFER,
|
||||
@ -613,6 +766,42 @@ open class TextInputPanel<out S : Screen>(
|
||||
color = textColor
|
||||
)
|
||||
|
||||
if (selection != null && (selection.isNotEmpty || selection.coversEntireLine(line))) {
|
||||
val (before, selected) = selection.sub(line)
|
||||
|
||||
var x = 0f
|
||||
|
||||
if (before.isNotEmpty()) {
|
||||
x = font.width(before).toFloat()
|
||||
}
|
||||
|
||||
val width = if (selection.coversNewline(line)) this.width - x else font.width(selected).toFloat()
|
||||
|
||||
RenderSystem.setShader(GameRenderer::getPositionShader)
|
||||
RenderSystem.setShaderColor(0.0f, 0.0f, 1.0f, 1.0f)
|
||||
RenderSystem.disableTexture()
|
||||
RenderSystem.enableColorLogicOp()
|
||||
RenderSystem.logicOp(GlStateManager.LogicOp.OR_REVERSE)
|
||||
RenderSystem.disableDepthTest()
|
||||
RenderSystem.defaultBlendFunc()
|
||||
|
||||
val builder = tesselator.builder
|
||||
|
||||
builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION)
|
||||
|
||||
builder.vertex(stack.last().pose(), x, y + font.lineHeight + 2f, 0f).endVertex()
|
||||
builder.vertex(stack.last().pose(), x + width, y + font.lineHeight + 2f, 0f).endVertex()
|
||||
builder.vertex(stack.last().pose(), x + width, y, 0f).endVertex()
|
||||
builder.vertex(stack.last().pose(), x, y, 0f).endVertex()
|
||||
|
||||
tesselator.end()
|
||||
|
||||
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f)
|
||||
RenderSystem.disableColorLogicOp()
|
||||
RenderSystem.enableTexture()
|
||||
RenderSystem.enableDepthTest()
|
||||
}
|
||||
|
||||
y += font.lineHeight + 2f
|
||||
}
|
||||
|
||||
@ -678,6 +867,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
}
|
||||
|
||||
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
|
||||
selections.clear()
|
||||
requestFocus()
|
||||
return true
|
||||
}
|
||||
@ -745,7 +935,7 @@ open class TextInputPanel<out S : Screen>(
|
||||
|
||||
private fun greedyAdvanceLeft(input: String, position: Int): Int {
|
||||
if (position <= 1)
|
||||
return 0
|
||||
return -1
|
||||
|
||||
@Suppress("name_shadowing")
|
||||
var position = position
|
||||
@ -768,6 +958,12 @@ open class TextInputPanel<out S : Screen>(
|
||||
private fun greedyAdvanceRight(input: String, position: Int): Int {
|
||||
@Suppress("name_shadowing")
|
||||
var position = position
|
||||
|
||||
if (position < 0)
|
||||
position = 0
|
||||
else if (position >= input.length)
|
||||
return position + 1
|
||||
|
||||
var type = CharType[input[position]]
|
||||
|
||||
while (position < input.length) {
|
||||
|
Loading…
Reference in New Issue
Block a user