Jline added. UX fixed

This commit is contained in:
2026-04-15 10:42:52 +03:00
parent f088d05731
commit 73970c192a
3 changed files with 59 additions and 53 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ shadowJar {
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
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')
}
+56 -52
View File
@@ -1,21 +1,63 @@
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.FileNotFoundException
import java.util.ArrayDeque
import java.util.Scanner
import runner.CommandValidator
/**
* Реализация [input.IOManager] для чтения потока ввода/файла.
* Поддерживает корректную обработку Ctrl+C и Ctrl+D.
*/
class IOManager() {
val isInteractive = true
val inputQueue = ArrayDeque<Scanner>()
val fileHistory = ArrayDeque<String>()
class IOManager {
val isInteractive: Boolean = true
val commandValidator: CommandValidator = CommandValidator()
init {
inputQueue.push(Scanner(System.`in`))
private val terminal = TerminalBuilder.builder()
.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) {
@@ -23,53 +65,15 @@ class IOManager() {
printErrConsole("Скрипт уже был выполнен: $filePath")
return
}
try {
fileHistory.push(filePath)
inputQueue.push(Scanner(File(filePath), Charsets.UTF_8))
scriptQueue.push(Scanner(File(filePath), Charsets.UTF_8))
} catch (e: FileNotFoundException) {
printErrConsole("Файл не найден: $filePath")
}
}
// сделать основной Scanner
// при инициализации стандартный сканер добавляется
// стек input, один (console) открыт постоянно
// если файл - в стек кидается Scanner
fun print(text: String) = 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)
}
}
fun printErrConsole(text: String) = System.err.println(text)
}
@@ -18,6 +18,8 @@ class CommandValidator {
"exit"
)
fun getKnownCommands(): Set<String> {return knownCommands}
fun validate(command: String, args: List<String>?): Pair<Boolean, String?> {
if (command !in knownCommands){
return Pair(false, "Неизвестная команда: '$command'. Введите 'help' для справки.")