Jline added. UX fixed
This commit is contained in:
+1
-1
@@ -23,7 +23,7 @@ shadowJar {
|
|||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'org.jetbrains.kotlin:kotlin-test'
|
testImplementation 'org.jetbrains.kotlin:kotlin-test'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0'
|
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0'
|
||||||
implementation "io.ktor:ktor-network:3.4.2"
|
implementation "org.jline:jline:3.30.6"
|
||||||
implementation project(':common')
|
implementation project(':common')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,63 @@
|
|||||||
package input
|
package input
|
||||||
|
|
||||||
|
import org.jline.reader.EndOfFileException
|
||||||
|
import org.jline.reader.LineReader
|
||||||
|
import org.jline.reader.LineReaderBuilder
|
||||||
|
import org.jline.reader.UserInterruptException
|
||||||
|
import org.jline.reader.impl.history.DefaultHistory
|
||||||
|
import org.jline.terminal.TerminalBuilder
|
||||||
|
import org.jline.reader.impl.completer.StringsCompleter
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileNotFoundException
|
import java.io.FileNotFoundException
|
||||||
import java.util.ArrayDeque
|
import java.util.ArrayDeque
|
||||||
import java.util.Scanner
|
import java.util.Scanner
|
||||||
|
import runner.CommandValidator
|
||||||
|
|
||||||
/**
|
class IOManager {
|
||||||
* Реализация [input.IOManager] для чтения потока ввода/файла.
|
val isInteractive: Boolean = true
|
||||||
* Поддерживает корректную обработку Ctrl+C и Ctrl+D.
|
val commandValidator: CommandValidator = CommandValidator()
|
||||||
*/
|
|
||||||
class IOManager() {
|
|
||||||
val isInteractive = true
|
|
||||||
val inputQueue = ArrayDeque<Scanner>()
|
|
||||||
val fileHistory = ArrayDeque<String>()
|
|
||||||
|
|
||||||
init {
|
private val terminal = TerminalBuilder.builder()
|
||||||
inputQueue.push(Scanner(System.`in`))
|
.system(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private val history = DefaultHistory()
|
||||||
|
|
||||||
|
private val lineReader: LineReader = LineReaderBuilder.builder()
|
||||||
|
.terminal(terminal)
|
||||||
|
.history(history)
|
||||||
|
.completer(StringsCompleter(commandValidator.getKnownCommands()))
|
||||||
|
.option(LineReader.Option.HISTORY_IGNORE_DUPS, true)
|
||||||
|
.option(LineReader.Option.DISABLE_EVENT_EXPANSION, true)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private val scriptQueue = ArrayDeque<Scanner>()
|
||||||
|
private val fileHistory = ArrayDeque<String>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Читает строку.
|
||||||
|
* Если есть активный скрипт — читает из него.
|
||||||
|
* Иначе — интерактивный ввод через JLine.
|
||||||
|
*/
|
||||||
|
fun readLine(prompt: String): String? {
|
||||||
|
// Режим скрипта
|
||||||
|
while (scriptQueue.isNotEmpty()) {
|
||||||
|
val scanner = scriptQueue.peek()
|
||||||
|
if (scanner.hasNextLine()) {
|
||||||
|
return scanner.nextLine()
|
||||||
|
}
|
||||||
|
scanner.close()
|
||||||
|
scriptQueue.pop()
|
||||||
|
fileHistory.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
lineReader.readLine(prompt)
|
||||||
|
} catch (e: UserInterruptException) {
|
||||||
|
""
|
||||||
|
} catch (e: EndOfFileException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addScriptScanner(filePath: String) {
|
fun addScriptScanner(filePath: String) {
|
||||||
@@ -23,53 +65,15 @@ class IOManager() {
|
|||||||
printErrConsole("Скрипт уже был выполнен: $filePath")
|
printErrConsole("Скрипт уже был выполнен: $filePath")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fileHistory.push(filePath)
|
fileHistory.push(filePath)
|
||||||
inputQueue.push(Scanner(File(filePath), Charsets.UTF_8))
|
scriptQueue.push(Scanner(File(filePath), Charsets.UTF_8))
|
||||||
} catch (e: FileNotFoundException) {
|
} catch (e: FileNotFoundException) {
|
||||||
printErrConsole("Файл не найден: $filePath")
|
printErrConsole("Файл не найден: $filePath")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// сделать основной Scanner
|
fun print(text: String) = println(text)
|
||||||
// при инициализации стандартный сканер добавляется
|
|
||||||
// стек input, один (console) открыт постоянно
|
|
||||||
// если файл - в стек кидается Scanner
|
|
||||||
|
|
||||||
/**
|
fun printErrConsole(text: String) = System.err.println(text)
|
||||||
* Читает строку с промптом ☭.
|
}
|
||||||
*/
|
|
||||||
fun readLine(prompt: String): String? {
|
|
||||||
while (inputQueue.isNotEmpty()) {
|
|
||||||
if (inputQueue.size == 1) kotlin.io.print(prompt)
|
|
||||||
val topInDeque = inputQueue.peek()
|
|
||||||
if (topInDeque.hasNextLine()) {
|
|
||||||
|
|
||||||
return topInDeque.nextLine().toString()
|
|
||||||
}
|
|
||||||
if (inputQueue.size > 1) {
|
|
||||||
topInDeque.close()
|
|
||||||
inputQueue.pop()
|
|
||||||
fileHistory.pop()
|
|
||||||
|
|
||||||
// snap back to reality
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
inputQueue.pop()
|
|
||||||
print("Ввод закончен.")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun print(text: String) {
|
|
||||||
println(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun printErrConsole(text: String) {
|
|
||||||
System.err.println(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -18,6 +18,8 @@ class CommandValidator {
|
|||||||
"exit"
|
"exit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun getKnownCommands(): Set<String> {return knownCommands}
|
||||||
|
|
||||||
fun validate(command: String, args: List<String>?): Pair<Boolean, String?> {
|
fun validate(command: String, args: List<String>?): Pair<Boolean, String?> {
|
||||||
if (command !in knownCommands){
|
if (command !in knownCommands){
|
||||||
return Pair(false, "Неизвестная команда: '$command'. Введите 'help' для справки.")
|
return Pair(false, "Неизвестная команда: '$command'. Введите 'help' для справки.")
|
||||||
|
|||||||
Reference in New Issue
Block a user