diff --git a/client/build.gradle b/client/build.gradle index e2f3241..4de3ff8 100644 --- a/client/build.gradle +++ b/client/build.gradle @@ -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') } diff --git a/client/src/main/kotlin/input/IOManager.kt b/client/src/main/kotlin/input/IOManager.kt index 5e6e8de..22a490a 100644 --- a/client/src/main/kotlin/input/IOManager.kt +++ b/client/src/main/kotlin/input/IOManager.kt @@ -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() - val fileHistory = ArrayDeque() +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() + private val fileHistory = ArrayDeque() + + /** + * Читает строку. + * Если есть активный скрипт — читает из него. + * Иначе — интерактивный ввод через 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) +} \ No newline at end of file diff --git a/client/src/main/kotlin/runner/CommandValidator.kt b/client/src/main/kotlin/runner/CommandValidator.kt index 57b5e60..4ef19a1 100644 --- a/client/src/main/kotlin/runner/CommandValidator.kt +++ b/client/src/main/kotlin/runner/CommandValidator.kt @@ -18,6 +18,8 @@ class CommandValidator { "exit" ) + fun getKnownCommands(): Set {return knownCommands} + fun validate(command: String, args: List?): Pair { if (command !in knownCommands){ return Pair(false, "Неизвестная команда: '$command'. Введите 'help' для справки.")